{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Verify Installation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'1.0.1.post2'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import torch\n",
    "\n",
    "# get Pytorch version\n",
    "torch.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'0.2.2'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# import torchvision\n",
    "import torchvision\n",
    "\n",
    "# get torchvision version\n",
    "torchvision.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# checking if cuda is available\n",
    "torch.cuda.is_available()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "4"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get number of cuda/gpu devices\n",
    "torch.cuda.device_count()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'GeForce GTX TITAN X'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get cuda/gpu device id\n",
    "torch.cuda.current_device()\n",
    "\n",
    "# get cuda/gpu device name\n",
    "torch.cuda.get_device_name(0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Tesnor Data Type"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1.],\n",
      "        [1., 1.]])\n",
      "torch.float32\n"
     ]
    }
   ],
   "source": [
    "# define a tensor with default data type\n",
    "x = torch.ones(2, 2)\n",
    "print(x)\n",
    "print(x.dtype)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Specify Data Type"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1, 1],\n",
      "        [1, 1]], dtype=torch.int8)\n",
      "torch.int8\n"
     ]
    }
   ],
   "source": [
    "# define a tensor\n",
    "x = torch.ones(2, 2, dtype=torch.int8)\n",
    "print(x)\n",
    "print(x.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Change Tensor data type"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.uint8\n",
      "torch.float32\n"
     ]
    }
   ],
   "source": [
    "# define a tensor with type torch.uint8\n",
    "x=torch.ones(1,dtype=torch.uint8)\n",
    "\n",
    "print(x.dtype)\n",
    "# change a tesnor data type\n",
    "x=x.type(torch.float)\n",
    "print(x.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Converting Tensors to NumPy arrays"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[0.0805, 0.8540],\n",
      "        [0.2553, 0.2066]])\n",
      "torch.float32\n",
      "[[0.08054882 0.8540349 ]\n",
      " [0.25528336 0.20658517]]\n",
      "float32\n"
     ]
    }
   ],
   "source": [
    "# define a tensor\n",
    "x=torch.rand(2,2)\n",
    "print(x)\n",
    "print(x.dtype)\n",
    "\n",
    "# convert tensor to numpy array\n",
    "y=x.numpy()\n",
    "print(y)\n",
    "print(y.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Converting NumPy arrays to Tensors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0. 0.]\n",
      " [0. 0.]]\n",
      "float32\n",
      "tensor([[0., 0.],\n",
      "        [0., 0.]])\n",
      "torch.float32\n"
     ]
    }
   ],
   "source": [
    "# import NumPy\n",
    "import numpy as np\n",
    "\n",
    "# define a NumPy array\n",
    "x=np.zeros((2,2),dtype=np.float32)\n",
    "print(x)\n",
    "print(x.dtype)\n",
    "\n",
    "# convert array to PyTorch Tensor\n",
    "y=torch.from_numpy(x)\n",
    "print(y)\n",
    "print(y.dtype)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Move Tensors between devices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1.5000, 2.0000])\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "# define a tensor\n",
    "x=torch.tensor([1.5, 2])\n",
    "print(x)\n",
    "\n",
    "print(x.device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1.5000, 2.0000], device='cuda:0')\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "# move tensor onto GPU\n",
    "# define a cuda/gpu device\n",
    "device = torch.device(\"cuda:0\")\n",
    "x = x.to(device)\n",
    "print(x)\n",
    "print(x.device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([1.5000, 2.0000])\n",
      "cpu\n"
     ]
    }
   ],
   "source": [
    "# move tensor onto cpu\n",
    "# define a cpu device\n",
    "device = torch.device(\"cpu\")\n",
    "x = x.to(device)\n",
    "print(x)\n",
    "print(x.device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 1.],\n",
      "        [1., 1.]], device='cuda:0')\n"
     ]
    }
   ],
   "source": [
    "# define a tensor on device\n",
    "device = torch.device(\"cuda:0\")\n",
    "x = torch.ones(2,2, device=device)\n",
    "print(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loading and Processing Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([60000, 28, 28])\n",
      "torch.Size([60000])\n",
      "torch.Size([10000, 28, 28])\n",
      "torch.Size([10000])\n"
     ]
    }
   ],
   "source": [
    "from torchvision import datasets\n",
    "\n",
    "# path to store data and/or load from\n",
    "path2data=\"./data\"\n",
    "\n",
    "# loading training data\n",
    "train_data=datasets.MNIST(path2data, train=True, download=True)\n",
    "\n",
    "# extract data and targets\n",
    "x_train, y_train=train_data.data,train_data.targets\n",
    "print(x_train.shape)\n",
    "print(y_train.shape)\n",
    "\n",
    "# loading validation data\n",
    "val_data=datasets.MNIST(path2data, train=False, download=True)\n",
    "\n",
    "# extract data and targets\n",
    "x_val,y_val=val_data.data, val_data.targets\n",
    "print(x_val.shape)\n",
    "print(y_val.shape)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Display images"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([60000, 1, 28, 28])\n",
      "torch.Size([3, 152, 242])\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD1CAYAAABeMT4pAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAIABJREFUeJzsnXd4FEUbwH8bQugBQg0CoYYuEDqKIFKUDtJ7F1AUAUEUEASV+kmVpiIgAgoICHaK0qUqvTcBaSEQQiDJ3fv9ceyYI4WQ3N2GOL/nmSe5nbmd9/Z252beeYshImg0Go0m5eJltQAajUajcS96oNdoNJoUjh7oNRqNJoWjB3qNRqNJ4eiBXqPRaFI4eqDXaDSaFI7bBnrDMF40DOOYYRgnDcN42139aDQajSZ+DHfY0RuGkQo4DtQF/gZ2Ae1E5LDLO9NoNBpNvLhrRl8ZOCkip0UkAlgKNHVTXxqNRqOJB3cN9E8BF6K9/vvBMY1Go9F4GG83ndeI5ZiTjsgwjN5A7wcvK7hJDo1Go0nJXBeRHI9q5K6B/m8gX7TXeYFL0RuIyFxgLoBhGDrgjkaj0Tw+5xLSyF2qm11AUcMwChqG4QO0Bda4qS+NRqPRxINbZvQiEmUYxmvAT0Aq4HMROeSOvjQajUYTP24xr3xsIbTqRqPRaBLDHhGp+KhG2jNWo9FoUjh6oNdoNJoUjh7okwkVKlRg/vz5zJ8/H5vNpv4PCgqyWjSNJtkxdepURIQDBw5w4MABAgICrBYpWaMHeo1Go0nhpLjN2FSpUgGQOXNmp+OvvfYa6dOnB6BYsWK8+uqrTJo0CYB27dpx7949AMaNG8fo0aNdJU6CKFeuHBs2bMDX1zdG3a1bt8iWLZtH5UksL7zwAosXL6ZmzZoAHDt2zGKJHAwfPhyA0aNH4+XlRa1atQD47bffLJTqySBTpkwAZMyYkYYNG5IzZ04AJk+ezP379z0uT4ECBQDYs2cPWbJkwRy/GjZsyE8//eRxeeIiMDCQ1KlT89xzzwHwySefYLfb42y/evVqANq2bUtERMTjdJWgzVh3OUx5hPz58+Pj4wNA9erVefbZZ8mSJQsAL7/8cpzv+/vvv5k2bRrNmzcHIDQ0lD///BPw7MNfuXJlAFasWEHmzJnVTRsaGqq+7GzZslGtWjX27NkD8Lg3QQzMGy9btmx8++23STrXw1SqVIndu3e79JxJpWvXrrz9tiN4qvmgJYfJTXKmYMGCAAwZMoRq1aoBULp0aac2uXPn5vXXX/e4bNeuXQPg999/p0mTJh7vPz5KlSpF165dAWjVqhVeXl7kyZMHcNx78d135meZPXs2AwYM4Pbt2y6V7Ykc6MuXLw/A+vXrY8zc48N80IcPH05YWBhfffUVAJcuXeLmzZuAZ2ah6dOnJygoiC+//BIAf39/p/oTJ04wYcIEAJYuXcqWLVsYMWIEAB9++GGS+jZns0WLFnXZQO/l5dAAFixYkPz582MYsUXAsIaAgADSpEljtRhUqVIFgE6dOqkf21KlSgEwePBgwHEf1qhRA4BFixaxc+dOj8pYvHhxBgwYQMeOHQFImzat+i4vXLhAaGgoJUqUAKB169Z88sknHD161KMyhoWFAXDuXIIcQj3KRx99RIMGDZJ0js6dO/PZZ5+xdetWF0nlQOvoNRqNJoXzRM7ozV/zGzduxDujN2dEISEhPP/880rtsWjRIvcLGQ9z5syhXbt2cdYHBQWRMWNGwKFKqlWrFmXKlHFJ3507dwZg+/btLjkf/Lsi6dWrF19++aXHZ3lxUadOHfr3769eHz16lEaNGnHlyhWPytGmTRumTp0KQPbs2dUsedOmTeTIkYOJEyeqtmZd9uzZadu2rdtly5w5M+PHj1dymjp5kxMnTgBQv359fHx8OHLkiJIve/bsbpfvYUzVbNmyZT3e96P45ZdfnGb0V69e5fPPPwcc32t01U21atXUXpYneCIH+uDgYADeeustGjVqBMC+ffuYNm2aarN//37q1q0LOJZ7pUqV4o033vC8sNGoUMERpLNhw4ZO6o3ffvuNtWvXqgf+8uXL7Nu3D4CbN29Su3Ztl6lDTDWLK/n000/V/+bAYCXPPvssAF988YXTRGDixIkeW/J7ezserUqVKjFv3jxlCPD7778zZswYALZs2UKaNGn4+uuvAahXr556v6f2Opo3b07Pnj1jrTt16pR6hi5cuEDRokU9IlN8mNcxf/78TscrVarE0aNHLVXpzJo1i1WrVqnXkZGR/PPPP7G29fX15eDBgwBKjw+watUqt3z3WnWj0Wg0KR0RsbzgiFWfqOLr6yu+vr5iGIbMnTtXbDab2Gw2ad++faLP6Y5Srlw5CQ4OluDgYImKipKoqCj57rvv5LvvvpOMGTNKw4YNZdiwYTJs2DDJkSOH03ttNpuEhoZKaGioBAUFJVqGp59+WsLCwiQsLEwWLVrkss+2bds22bZtm9jtdqlatarl13revHkyb948dS+sX79e1q9f71EZunbtKl27dlXf9Q8//CA//PCD+Pr6OrXr2LGjahMVFSXnzp2Tc+fOxbgH3FXWrVvn1P/JkydlyZIlsmTJEsmfP79T28aNGzu1ffbZZy37jkeMGCE2m81Jntdee83yey+hpVWrVuqZjv4ZpkyZ8rjn2p2gMdbqQT6pA330MnHiRPVwb9iwQby8vMTLy8vSLzQwMFACAwNl8eLFSrYrV67I/v37pWXLltKyZctHniP6Db148eJEy/L222+L3W4Xu93usoE+V65ccvnyZbl8+bLY7XbJly+fpdc7e/bs6jpHRkbKtWvXpHbt2lK7dm2PyTB27FglQ1RUlEybNk1NSB5ue+TIEacHvWnTptK0aVOPyZonTx4ZNWqUjBo1SqpXry45c+aMs23Pnj2TzUAPPLEDfdu2bWX9+vVOspsltnvkEeW/N9BnyJBBNmzYIBs2bBCbzSb16tWTevXqWfaFpkmTRtasWSNr1qyRqKgoCQkJkZCQEKlfv75ky5ZN8ubNK3nz5n2sG3rz5s2Jlmf+/PlqoH/77bdd8hkXLVqkznn06FHJkiWLJde6QIECUqBAAdmzZ4/TQD9y5EiPyjFy5Eix2WwSHh4u4eHhsmrVKkmXLp1Tm7Rp00ratGmlSZMmEhYWpuQdPXq0ZfdqQspnn32WrAZ6EVHXzmazJeuBvkOHDnLw4EE5ePCghIeHxxjgd+/eLbt3745xrySgJGig1zp6jUajSeE8kVY3cREWFkavXr0A2Lt3L/PmzQNg48aN7N69m5kzZwJ4zDMyKCjIydyqadOmQPJwvd+1a1ei3ufr68uLL76onGqiW4qMGTOGkJAQl8j3uLz44osAPP300+rY+vXrlVmjuzHN/vr164eIKHf8Zs2aObUrUqQIixcvBv61wlq+fDmAcpKzGtPjNUOGDMraS0ScTHy3bdvmUhPdxPAob1NPU6BAATp16gQ4THuj8+yzz8aQ1fR+ffvtt/n+++8BCA8Pd4tsKWqgB4dJGDhc3+fPnw84vBE7depEhgwZAFi4cCGXL192uyyTJ09WD8pvv/2W6AHey8tLefW6yszSz88vxjHTNtnLy4sXXniBvHnzAuDj40OHDh1UXXh4uPJRuH//vjIlNMM0eJpmzZoxbtw49XrLli0AdOnShVu3bnlEBjMUh2lbbg6WOXPmpFu3bsrFvXTp0spHwlxWmx7SptenpzFNFkuVKsXIkSOdJiemOa55/5nPTbdu3bDZbB6WNPlSpkwZVq9eHcPsMz42b94MwNy5c90llkKrbjQajSaFk+Jm9CbffvstJ0+eBBwz6xdeeEHFiQkICOCDDz7g4sWLbuu/UaNGlCtXTi3X1qxJfG706EvU/fv3J/o84eHh6jyzZ8/mnXfecao31R6GYRAVFcXdu3cBOHz4sPLw2717N7/99pvyLv37779Jly4dgCUesQUKFGDFihVOx06fPg3gUQ9Y0+v62rVr5MiRgzNnzgAx1YSXLl1SS3Z/f3+uX7/Od9995zE5TVKnTg044kaZ18/f35/w8HA1a9+2bZtSiZmzfjM6bIsWLZg6dWqSg+ylJAzDiHPFHX1VbmI6ezZo0ECpbtxFogd6wzDyAQuB3IAdmCsiUw3D8AOWAQWAs0BrEbmZdFEfnwMHDgCOAEyNGzdWqpxXXnmFokWLKq8/d5AuXTp8fHy4evUqAMuWLXus95uBuEaNGgXAhg0bAFQkxsTQr18/5TlYvXr1GPXnz58HHCFTDx8+zI4dO+I8V+/evQHIkSOHGlitYOjQoTEeoOhqHE9h7k00a9aMtWvXKtXYqVOnWL16NV988QXg8OpeunQp4BhYzf89iY+PjxrAV65cqY6PHj2aDRs2qIBafn5+6r4zo1fmyJEDcATwOn/+vPIEtSJk8cOD53PPPceMGTM8Lgc4xppatWqpvauffvpJhT5/mB49ejiF5vAISTCJ9AeCHvyfCTgOlAQmAG8/OP42MN5T5pWPKvfv35f79++LzWaT+/fvS61ataRWrVpu6atVq1YSFRUlZ86ckTNnzjzWe9OkSSNjxoyRMWPGKCea+vXrS/369S03EzPLsmXLZNmyZWK322X8+PEyfvx4j/Zfrlw5KVeunJw6dUoiIyNVWb58ueXXJr7y3HPPiYnNZpP+/ft7tP/UqVPLRx995GTat3btWlm7dq0yjc2RI4fkyJFDdu3apUwXw8PDZfTo0bJixQpZsWKFeu+PP/4oP/74o9SuXVvKly+viic+y8N29FFRUVKyZEkpWbKk5d9zfCVz5sxOMjdo0CAp53OveaWIXBaRvQ/+DwWOAE8BTYEFD5otAJrFfgaNRqPReAKX6OgNwygAlAd2ArlE5DI4fgwMw8jpij4Sg6lzbtmyJZUqVVLWIeDQO//+++9ul+FxdPPlypUDHMHa2rRpAzjUKPElUUkORA/k5Cl+/vlnALJmzQr8G6nUTPyQXEmXLp1TAhRPqW5M3fqYMWMYPHiwsvAZNmwYS5YsARzqp0qVKjF9+nTAob83g9T17duXjRs3qixo1atXp0OHDsqayPw+wBEAzUxe4k5mz57NK6+84nTMVCkOGDDA7f0nlvr163u8zyQP9IZhZARWAANE5HZCzf8Mw+gN9E5q/w9TrFgxAPr3768ySOXOndupjc1m4/Lly/Gm9koq5saMaUf9qMiZAwcOVCnvMmfOrGytzbDCGmfM9Irmd2j6SNy5c8cymRKCVenuzAFw8ODB3L17Vw2QP//8M1WrVgUcJpMNGjQgbdq0ALz//vtqX+vChQvAv7bfP/74Iz/++KMKt22a3wK8+eabHvhE1mz+Ryd16tTKj2TDhg2PtIHv3r07AFOmTHG7bA+TJPNKwzBS4xjkF4uIuatzxTAM/wf1/sDV2N4rInNFpKIkIN+hRqPRaJJAEjZjDRxWN1MeOj4R583YCZ7YjM2dO7cMHDhQTp06JadOnXKKgWGWnTt3ys6dO6VJkyZu33AxN2PNDeBp06apDcR8+fJJq1atVBycc+fOic1mUxu3S5YskapVqyaLSJBxFXMzVkSkc+fO0rlzZ4/1PX/+fKcNTZvNJgEBARIQEGD5dXlUqV+/vlPAM09FqTQDz0VFRUlYWJjs2bNH9uzZI0ePHo2xoTl8+HAZPny4pEqVyvLr9ahy/PhxOX78uLqmJoULF3ZbnzVq1JAaNWrIDz/8oK5ZXMH8/Pz8xM/PTzp27Cg3b96UmzdvqveY0Suff/75pMiToM3YpKhungE6AQcMwzCNu98BxgFfG4bRAzgPtEpCH/GSK1culXdz+vTpFC9ePNZ2O3fuZOLEiSrTujtVNg9j6kb79eundO23b9+OkcRh+/btypRt5MiRHpMvqYiIW5KZxIa5h1G3bl31HUZERDBz5kyPZ41KLIULF7akXzMBRo4cOUiTJo1ThibThvv3339n1apVnD17FuCJ8Hw9dOgQAIUKFQI882ybexjRE6YPGTKE0NDQGG1NE+6goCAnn4pNmzYxa9YswBGixd0keqAXkS04ZvWx8UJiz/so/Pz8mDNnDuB48M0v+GG2bdvG5MmTAYde1F0xJOJi+/bt7Nq1i0qVKqlj5l5Brly5AEcqRHAkALc6+1VSqFatGoCyFXcXZjwZ8/oBXLx4USXXfhLYvHlzjLACnsBMSN6sWTOCgoKUf8fnn3/OzZsON5cn0fnJDB/QuHFjS+Xo27fvI9uY1/y7777jjTfeiNPO3h3oEAgajUaTwnkiQiBUqVIFcJgdVq5cmaeeeirWduHh4Spa4YcffmhZkChwhAZo0aKFsm4wLWpMpk6dyuzZs4HkkWc1sbgqyNp/hQMHDqjvu1ChQhQuXJhr1665vV9TrbBo0SIWLVrk9v48xeHDhwE4cuQIJUqU8Eif3bp1A+C1116jS5cucbY7deqUCiOyefNmFU3X9Nj3JEZyCPNpGEa8Qpgu7W+99ZbT8SNHjqg4ITabjUmTJlkWJve/hmmv/vnnn6sb+GGbZldjqr6WLVumEoCfOXOGIkWKuLVfV2Neu08//ZTffvtNucObg5bmySBNmjTquxw7dixZs2ZVPiW//PILq1evjjM5uAvZkxDLxSdioNdoUhKm09HXX39NnTp1VLyZbt26WboK1TyRJGig1zp6jUajSekk1o7elYVkYI+riy6eLr6+vjJ9+nSngFxWy6TLE1cSZEevVTcajUbz5KJVNxqNRqPRA71Go9GkePRAr9FoNCkcPdBrNBpNCkcP9BqNRpPC0QO9RqPRpHD0QK/RaB5JYGAgp0+f5ty5c5w7d85qcTSPiR7oNRqNJoXzRESv1Gg01mAm2WjTpg1+fn6sXbvWYok0iUEP9E8Y69evV6GBa9eu7bZ+SpYsSaNGjQDo1asXu3btAmD/fkcyMTPB8ZOYrEITP2Zil5UrV6rE4SLCwYMH6dGjh5WiaRJJihvoU6dODUD16tX58MMPAXjmmWesFMklfPzxx4Djcy1cuNCtfb3yyitMnDiRjBkzqmNmCry2bdsCsHv3bgCV/vC/RsaMGWnTpg0A9+7do0KFCgBkypSJDh06sGnTJsCRAethzNC1q1evVtcxuRAYGMikSZOAf/NAAAwbNozdu3errGhWYU5ylixZQoMGDShZsiTgyP+giRuto9doNJqUjgsiT6YC9gFrH7wuCOwETgDLAB9PRq/Mnj27ZM+eXex2u1y6dEkuXbokuXPntjrCXJLKuHHjJDw8XMLDw+X27dvSunVrad26tdv68/Pzk3/++UfsdnucJTg4WIKDg6VevXqWXx8ryoQJE+K9PgkpUVFR8tdff8k777wj77zzjhQsWNDyz1WtWjUVTTMqKkpsNpvYbDZp166d5bIBkj59ekmfPr38/fffYrfbpWfPntKzZ0/L5bKwJCh6pStUN28ARwDfB6/HAx+LyFLDMGYDPYBZLujnsTEzEuXOndsTmV7cRtWqVZVKasuWLXz99ddu7S84OJhRo0apJXz69Ok5f/48APnz5wf+TdRdv359fv75Z7fK40oCAgJIly4dAO3atXNK6rxu3TqVJu5RtGjRItbjN27c4K+//opx/NixYwAUK1ZMXbvy5ctTunRpxo4dC8Cff/7JmTNnEv5hXExgYCCLFy92Sg9pfs7Vq1dbJZYTZmq+48ePkydPHnLmzGmxRAln0KBBAPj4+FCiRAk6dOig6o4ePQpAqVKl3NJ3klQ3hmHkBRoCnz54bQC1geUPmiwAmiWlD41Go9EkjaTO6KcAQ4BMD15nA0JEJOrB67+B2DN5e4DknLj6ueee49133wUcM8vg4OBY27Vr147SpUtz6tQpAAYPHuwR+WbPnq1ywJYtW5bbt2/H2m7mzJkekScp1KlTB3DMTtu1a0fmzJkBTLWhwrQwSQj169enWLFiwL+zdXDMOC9fvhzvezNlcjwuBw4cUCskgCZNmrBu3boEy+BqOnXqRP78+fn+++8B6NOnT6ybycmBmTNnUqtWLYoXL261KHFSs2ZNSpcurf5v3rw58O+4FP3+K1q0KODIG2xuMLuUJOjmGwGfPPi/FrAWyAGcjNYmH3Agjvf3BnY/KC7TWUXX0ZulWrVqVuvRYpSjR48q/eezzz4bZ7uDBw+KiEjz5s2lefPmHpWxZcuW0rJlS9m7d2+ceuYSJUpYfi3jKp9++qns3Lkzhsy3bt2SW7duySeffCLdu3eX7t27S9q0aT0mV/v27aV9+/ZKHnP/pVKlSpZcp23btsm2bdvk7t27cvLkSSlatKgULVrU8u8vvpIvXz6x2+1y7949uXfvnvj7+1sqj7+/v2zatEnOnz+vyq1bt5z2Ov744w/5448/nPZAHi7nzp173L7drqN/BmhiGEYDIC0OHf0UIIthGN4PZvV5gUuxvVlE5gJzwf0ZpipUqMD27dvd2cVjc/fuXfWLnjZt2hj15cqVAxw6cbvdHmsbd7N8uUMDt2XLFn766ScAypQp49Tm/fffp1WrVh6XLS6yZcvGRx99BED37t0JDg5mz549AIwbN46DBw8SHh4OoPYdPIGPjw8A06ZNo3Pnzk511atXB2Dfvn0ek8ekadOmyoxSRPjmm2/U9UnuGIahrmuTJk2YM2eOx2UwV4vz5s0jX758cbYrWbIk169fByB79uzkyZOH+fPnA5A3b17V7vDhw26RM9E6ehEZJiJ5RaQA0BbYICIdgI1AywfNugDJYxdHo9Fo/qO4w2FqKLDUMIyxOMwuP3NDH3ESFeXYHrh165bSxZrOPsmFMWPGUKZMGbXT/ueffzrVZ8iQgaFDhwIOi5cdO3ao2bUnMa0Cnn76aaVrfJitW7d6UqRHMmLECOW9OX36dN59913u3LljqUy1a9emY8eOAHTt2lUdj4yM5PXXX+fIkSMel8m0/KlRo4bT8Zs3b8bpfPTGG284zVo9tV8UF9F13ObM3tMMGTIEIMZs/v79+wwdOpSdO3cCzvs4N27c4I033nCayZ89exZw7JO4A5cM9CKyCdj04P/TQGVXnDcxhISEALB582blwp9cMG+GXr16ERUVxauvvgrAtWvXnNr973//U+qQS5cuedyzt3jx4qxcuZIiRYoA4O0d922yZs0aT4kVK+nTp2fo0KHqARkwYAAbN24E4KeffuLevXtWikflypX56aefSJUqVYw6EeHChQvYbDaPy2X2WaFCBby8HAt7u93O77//7tRu4MCBgEPW/v37ExAQoOoGDRqkBqvkumnrTurVqxdjA99UB3bq1CneSVD0QR7+NV811TuuJsWFQEiulClThpUrVwIOHd306dP57bffnNqYM6Tos74PPvjAYzKalChRgoIFC8Y7wJsMGDCA119/3QNSxc7w4cMZOnSo8i34+eefLR/co9O6detYB3lwzELXrl2rwiB89913rFq1CnBY5LiTmjVrAo4Zvd1uBxyDVPQQB+XKlePZZ58FHDpwgLCwMMARcqBYsWJqpdm2bdv/XPjiQYMGkT59evV627ZtjB49Goh9pZs1a1YAXnrpJZ577jmn95mWTu5Ch0DQaDSaFM5/YkafLVs2S/r19vZWutnPPvvMaYlcrVo13nnnHQAmT56Mn5+fUtcYhqECl1lhSfDtt98ydOhQxo0bB8RuFWTi7+/vKbFiZdiwYYgIS5YsAUhWs3lwRIAsUaIElSpVAhyruYepWLGi+vvee+8BjuigEyZM4OrVqy6XKVOmTBQsWFC9Nu3+Fy1axIkTJwgMDATgrbfeomnTpoBDpfDLL78wefJkAHx9fdmwYYPaB7MCwzBi+EJ4krlz56rv89atW7Rv3z5eD/w+ffoAjj06gEOHDgGOVZ/bPfeTGuvGFQU32LWuWbNG2SmHhIRYYlvbsWPHWOOGHDt2zOn4jh075MKFC+r15cuXLbUJNstLL70kL730krRr106VTp06SUhIiLq233zzjaUy7ty5U2w2m7Jdrlu3ruXXLbaSP39+yZ8/vwQFBanrOm/ePLHZbHH6KGzcuFG8vLzEy8vL5d9r9Ptv5MiRMnLkSAEkV65csmbNGlmzZo1ERUVJSEiIhISEyIwZMyRNmjRSunRpKV26tBw5ckQdnzFjhsevp2lHb5b+/ftb/h3HVxo3bqz8JaKiouTevXvSr18/6devX1LPnSA7eq260Wg0mpSO1bN5d83o33zzTUtn9G3atJHIyEj1K3758mV5/vnn5fnnn5dy5crJ+vXrY8z2zf8jIyPlwoULcuHCBSlcuLDls5HoxTAMGTVqlLq2J06ckICAAAkICHB731WqVJEqVaqIj4+POubn5yejRo1Sq6Vbt25JiRIlkrXHbvTSoUMH2bFjh+zYsSPWWf2QIUNkyJAhLu1z6NChTvde9LqtW7c61dWsWVNq1qwpEDOy5aRJkyy7bg/P6E0Zk2uJ/nxHRUVJ7969XXXuBM3oLR/k3TXQv/zyy+omCAsL88hAFL1s2LBBTp06pVzsH64vWbKkbNmyRbZs2RJjoI+KipKFCxfKwoULLb9BHy5p0qRxesAOHz4sefPmlbx587qtT39/f9mzZ49cu3ZNrl27Jh07dnSqz549uxrobTabVK9eXapXr275tUpo8fb2Fm9vb9m0aVOMgX7u3Lkyd+5cl/b30UcfqWu1cuVKdbxcuXJy8eJFVTdgwABVFxgYKKdPn461zory8EDvzvsvKeXDDz+UDz/8UETE6R4tVqyYq/rwWJjiZInpOAWOTZs0adJ4tP/Vq1ezcuVKLly4EGt99uzZnUKStmvXjoMHD6rXyTVjjrmRZPL555+7Xda9e/fi6+urnMi+/PJLp/oBAwao/3/99Ven6/gkYN6re/bscTK7A0c4XnfwYIIVYzPTbrerY08//bSyC0+bNi1nzpxRDla3bt1yi1wpCR8fH8qXLw84X9c33niDEydOeFQWraPXaDSalI7Vaht3qW4AOXz4sBw+fFjsdrt88sknli/jzJI5c2aZMWOGWsYdP37cEjmyZcsm2bJlk9WrV0v79u3jbevv7y/+/v5OFjd2u10KFSrkdjmHDRsmYWFhseqwjx07Jna7Xc6cOSNnzpyRoKAgy79ff39/ZcmSkExgqVKlklSpUsmvv/7q9NkiIiKkRo0aUqPo0ifZAAAgAElEQVRGDZfK97CuvVq1alKtWjXp06ePhISExGopduXKFWnQoIHl19YsD6tuktteVvr06aV3795O13HRokWyaNEiyZIliyv7+m+rbgCV+eipp55SrtzJgX79+tGnTx9lI127dm1L5Jg6dSoAjRs3JjAwULmxX7x4kZMnT6qE14GBgbz11luAw34aUPbUly7FGpzUpXz00UdERkaqZbAZMRAc3obff/+9yt5z8uRJt8sTH7lz5+bHH39UUT5Nb8i4yJUrl7o3H74Pjhw5wubNm10uY0REhMrUlD59erZs2QIQQ40DEBoaCsA333zjdu/NpNCgQQOmT59utRgq18C8efNo2bKlOv7mm28yY8YMAOWJ7Em06kaj0WhSOCl6Rm8iIkRERFgthgoI1bNnT0SEuXPnAtZtvJrZoQoWLEi1atVUMLCzZ89y+PBhtfFmzlLAcS2PHj3KqFGjAM95opr5a5M7U6ZMcYrZX7BgQY4dO+YU493MWTtkyBAGDhzodH3N7EOhoaFuiyG0Z88e2rVrBziCltWqVcupfsGCBYAj3o4ZI//huExWc+XKFfdlY0oCZrAyczZvZoabNm2aZTIBWK6fd6eOfsqUKTJlyhSx2+3SokULy/V2x48fl+PHj0tUVJR88cUXlstjlkmTJknfvn3j9NCMXq5fv265vMm59OrVK8Y127Nnj2zYsEGVPXv2yJ49e2K9vrdv35bbt2/LCy+8YPlnSe5l165d6rqtWbPGcnmKFy8u8+bNk3nz5klUVJQcPnzYEz4mWkffunVrwBEb2l2ZWx6HL774AnBkZbI6vG90Bg8eTJo0aciYMaM6Vq5cOTXrg3/N6erVq+dx+Z4kfv31V5YuXUrbtm3VMXNvIS5M88opU6awYsUKABXHXBM3+/fvV/tI0e9dqxgxYgRt2rRRr2fMmJFsInpqHb1Go9GkcIzYdto9LoSbcsYuXboUcMRXb9KkSbL5ddWkbNKkSUPz5s0BhyXN8ePHVTx3QGUWA9iwYYPKPmRFztgnmQIFCqiopQsWLGD27NmWyVKqVCnGjRvHSy+9BDgiW06dOtUps5Sb2CMiFR/Zymr9vDt19Lrooosunijjx4+XqKgoOXXqlJw6dcqVIQ4eVdwfvdIwjCyGYSw3DOOoYRhHDMOoZhiGn2EYvxiGceLB3/gNiTUajUbjVpKqo58K/CgixYGywBHgbWC9iBQF1j94rdFoNCkW0zlz4MCBDBw40BMqm8ci0Tp6wzB8gT+BQhLtJIZhHANqichlwzD8gU0iUuwR50qcEBqNRvPfJkE6+qTM6AsB14D5hmHsMwzjU8MwMgC5ROQywIO/OZPQh0aj0WiSSFIGem8gCJglIuWBMB5DTWMYRm/DMHYbhrE7CTJoNBqN5hEkZaD/G/hbREzPjuU4Bv4rD1Q2PPgba3ZjEZkrIhUTZBqk0Wg0mkST6IFeRP4BLhiGYerfXwAOA2uALg+OdQFWJ0lCjUaj0SSJpIZA6A8sNgzDBzgNdMPx4/G1YRg9gPNAqyT2odFoNJokkKI9YzUajSaF43arG41Go9E8AeiBXvPYFCpUiGXLlrFs2TIiIiIoXrw4xYsXt1osjUYTB3qg12g0mhSOHug1j0X16tXZv38/FStWpGLFisycOZMrV65w5coVq0V7YgkMDCQwMJANGzbg7++Pv7+/1SLFSq1atbDZbNhsNkSEmjVrWi2SJoGkqMQjnTp1on79+gCULVuWYsX+jbywY8cOGjduDPybRONJIEOGDGzatIk8efIA8Mwzz3D27FmPy9GwYUMAli9fzuzZs3n33XcBVJLp/xqZMmVSyS5u3bqVpOvQoEEDAJ577jl69uwJOBKimwlJkgNdu3alf//+Tomt//e//7Fw4ULAkZYyOcn7JDBs2DAAPvjgAyZMmADA22+7KTSY1SGKkxqmOHv27JI9e3ZZtWqV2Gw2uXHjhty4cUPWrl2rSmhoqNjtdjl8+LAcPnzY8pCmsZU8efJI2bJlVcmfP7/kz59funTpIna7XY4cOSJHjhyRTJkyeVy2okWLSlhYmISFhcmPP/4oXl5ell8vq8vYsWMlKipKoqKi5M0330zSuWrUqCE1atRQ54uKipIiRYpY/hkB6dq1q3Tt2lXWr18vkZGRqthsNqfXbk6X98gSEBAgU6ZMkYiICImIiBC73S5fffWVfPXVV5Zfw9hKpkyZ5NKlS3Lp0iWx2Wxy7949uXfvnvTo0eNxz+X+MMUajUajSf488aqbH3/8EXBkm5kwYQITJ04EIDg4WLUpXrw4f/zxB4GBgQCMHDmS999/3/PCPqBMmTL079+fgIAAdSwwMJD8+fOr1+PGjQOgZMmSGIbBxYsXAfDx8fGYnGnTpgVg3rx5HDhwAHDk4Y2+fE8u+Pn5AdCmTRveeecdAKXuGjFiBAAffvihW/p+7733OH36NACrVz++I3iuXLlcLVKiyZIlC+XKlQNg/vz55MiRA3BkzYJ/s2N5eXmp58lKunfvDsDHH3/MiRMneOWVVwDIly8f7733HuDI0Rw9q5fVeHt707dvX6fv3dzj2r59u3s6tVptkxTVTd26dcVms4nNZpMlS5bE2/b9999XGePPnDlj6bLt9ddfV3Kb5e7du7JgwQJZsGCB/P333051drtdOnbsKB07dvSonBMnTpSJEydKeHi45M2bV/LmzWv5kje2Uq1aNdm+fbts375dbDabkwokepk/f77L+oyuuomKipLg4GAJDg6WihUrPtZ5MmbMKNu2bZNt27Y5ne+dd97x+HVs1qyZrFu3Lk71TGRkpHTu3Fk6d+4sXbt2tVR14+PjI8OGDZPw8HAJDw+XDz74QLJkyaLqg4KC1PPz1FNPWX6PRi81atSI8fw3aNBAGjRokJjzadWNRqPRaJ5w1U3q1Kk5efIk8G8i8LhYvnw5w4cPBxwqCV9fX27fvu12GaMzatQoAN566y3AkdAY4Nq1a0yaNIlr164BUK5cOX766ScAsmfPzrVr11i+fLlHZU2TJg0dO3YEYNOmTfz9998e7T+hZM+enblz51KiRAnAcS1XrVoFONQonTt3plUrR7ilqlWr4uPjQ0RERJL7PXPmjNNrX19fAEaPHk3Hjh25efNmgs5TtGhRKleunGR5koL5PZv3o4mXV8x5oGEY8dZ7im7dujF27FgGDBgAwPTp053q69Wrx9WrjsC5ptrTagoUKADA1KlTnY6vX7+ejRs3urdzq9U2SVHdpE2bVtKnTy/p06d/ZNtixYop1Y3dbpc+ffp4fMlmqkJM9ZG/v7/4+/ur+iJFikiRIkXk66+/VnLeuXNH+vXr53FZR4wYIaGhoRIaGipBQUGWL3fjKlu3bpWoqCj5/vvv5fvvv49RX7RoUbl27Zpcu3ZNQkNDpWzZsi7pN1WqVDJixAgZMWJEDBVRz549E3yePHnyyPHjx+X48eOWWN107NhRrl+/LtevX5fIyEgJDQ2VkydPysmTJ+XKlStO6pnQ0FBp0aKFtGjRwjLVjZ+fn/j5+cnVq1dl2bJl4u3tLd7e3qo+ICBAAgIC5OTJk3L58mW5fPmy5feoWQ4cOCAHDhxQ6pqbN2/KzZs3pW7dukk5b4JUN0/0jP7evXsJbnv69GkOHz4MODY4ixYt6i6x4sSclb/00kuUKFFCbbj269ePzJkz87///Q9w2Kybm8kffPABn3zyicdlrVevHlu3bgVg7969Hu8/oYSHhwMJ2wS9ffs2169fd0m/NpuNadOmAdChQweKFCmi6l599VW+/fZbAG7cuBHveXLlykWhQoVcItPj0KxZM8Axi4++ub5z507q1KkDOGzn582bp+reeecdVq5cqeo8jbe3t7onr169St++fWPY7n/55ZeAI0zH5MmTPS5jfJQqVQrAnNyq5/qXX35xe99aR6/RaDQpnCd6Rv84REZGEhkZaakM+/fvBxwmVCVKlOCFF14AoG7dunz88cdO5pWjR48GYuoePUGNGjWoWrUqTz/9dKz1tWrVUvsJhw4d8qRoMTAMA8MwlE48bdq0FC5cGHDMOitUqMA///wDQPv27V2qrzU9rLdu3eo0oy9Tpgz58uUDYs7ofXx8lAkgoPYPPEnXrl2ZMmWKem2ujHfu3Mnrr7/u1PbPP/8EHDP/WbNmqePLly+nV69eHt1faNmypTLprF27tpMJNTi+36pVqwJw584dJk2a5DHZHsX//vc/tb8hIqxfv54xY8Z4rP//zECfJk0aZRcOEBoa6nEZ7t+/D6A2gc2YJitWrMAwDLWk++yzz9SGohV06NCBI0eOKNtw+HepPnnyZLJmzao+y+DBg5k5c6YVYgKO5bCIMHDgQAAGDRpEhQoVVH3btm3dvpG9fft2unTp4nSsWrVqgOPHvXr16lSvXh2AjBkzKqOA2DDtvRO6mZsYRowYQYYMGdRr07/go48+cmq3ZcsWfvjhB4AYsYzu3Lmj7gFP0aVLF44dOwbAtm3bnOpy587Nxx9/rDaIp0+fniziL5nPRrNmzdTz/ddff9GhQ4fHUj0nGas3YpOyGfs45eHN2KpVqzrVm6EUXnjhBRk+fLgUK1ZMihUr5hZZYrOjt9vt8t1338l3330ngYGBlm4aRURESMuWLdVrHx8fOXPmjJw5c0ZatmwpmTJlkjZt2kibNm3k7t278uKLL8qLL75oiayHDh2SsLAwtYkZ3Y7+9u3bUrJkSY/I8eWXX8qXX34Z43t9uIjII9vYbLbEuMInqJQrV06uXr2q+knKuTZu3KjO44nNWLvdLsOHD5fhw4erY76+vuLr6ytbt24Vm80mM2fOlJkzZ4qvr68l92P0UrlyZbl48aJcvHhRPeN2u1369u3ryn60Hb1Go9FoUrjqxnTbzps3L88884xT3ezZs9mzZw8AQUFByoU+X758hIaGKp2rK60LUqVKBTh04NHtkQHWrVunomtahWkV4O3t7WTNEBQUpEJNmGqQZcuWAfDss8+qKHxmG09SqlQpqlatSt68eZ3kAli5cqWytHI3poVH27Zt421nt9vVEj4+qlatymeffeYS2QBKly4NONSEWbNmTXIYi4wZM+Lj4+ORcBjmXhY4W1fVr1+fOXPmAJA/f35Onjypwl942kcmNrp37+4UcvrIkSNA4sJkJJUkDfSGYbwJ9MSxhDiAIzm4P7AU8AP2Ap1EJOkeKrGQLl06cubMCUCFChWoUqUKtWvXdqoHhznlw5QqVYrMmTOr159//jngGHBv3LgRwyHGFZhOXS1atIjxsCfk4Xc3uXPnVv+bulBwbLjGpVeeNWuWioNjFTt27KBMmTIxjrsrtk1SOHnypPqu161bx61btxg5cqTb+zVNQaNv+CeFli1bemwj1nR8unfvnvohz5QpEzly5FD7BIZhMHPmzGQTgnzAgAH06NHD6bmuW7cuAJcuXfK4PIlW3RiG8RTwOlBRREoDqYC2wHjgYxEpCtwEerhCUI1Go9EkjqSqbryBdIZhRALpgctAbaD9g/oFwChgVqzvTgTp0qVToQQaN24cZ67S27dvc+fOHQCioqLw9v73o3766afMnj3bI45AZgTFbt268fLLLwOO2fvevXuV6Vq3bt3UyiS5ED3kQXwWSsklNIKpmvDy8kpW0TWDg4M5f/68Uu0sWbLEqb58+fIemdE/zJAhQxL1PvN5MxNlmElw3GlBYq4Y+/TpQ48ejnnjn3/+yZIlS5gxYwYAu3fvVmocKzHNanv27ImXlxc2mw1wRIC1YiZvkuiBXkQuGoYxCTgPhAM/A3uAEBExFbx/A08lWcporFq1Si2B7t+/z7p16wBH7JHVq1erpdzZs2fVIHT06FECAwOVueDAgQPVj4C7MfWL0cMiDx8+nBkzZijvxG7dunlMlxwf5r7Bw/sH8VGzZk1LTFUfxvSQtdvtbNq0CcAlMW0SyqlTpwBYuHAhhQoVUvrYTz75JFGqrXr16pE1a1a3mVk+ymM3NooXL670y9myZePq1au0bNkSiGl+6Q4WLlyoMloZhsGUKVNUqN+XX37Zs+aKsVCkSBHWrFkDoLLbffzxxwAMHTrUMrkgCQO9YRhZgaZAQSAE+AZ4KZamsSqfDcPoDfR+3H7r1aun9Ocvv/wy+/bti7Wdt7c348ePBxybsVevXqV169YAHhvka9WqpXSjAE2aNAHg119/JXfu3E4zOSvSAz6MqU9MyH5B6tSpAccsa9GiRW6V61GUKFFCzfSuXbumHHs8eU3NzT8zPnpSeeqpp1yae8D88TbtzOfPnw+gBs64MNMlLly4kKZNm6rjp0+fplGjRk57OZ6kZs2avPbaa3zwwQcA7Nq1yxI5olOsWDGn9KWAGvitJimqmzrAGRG5BmAYxkqgOpDFMAzvB7P6vECs6xURmQvMffBe63ciNRqNJoWSlIH+PFDVMIz0OFQ3LwC7gY1ASxyWN10Al9oSiQghISEAsS6JTe/Xb775RiW0vn//Pm3btvV4cK66desqy57ffvuNtWvXAo7ZcKNGjVSdYRguC7aVFEz10eXLl+nYsaOTy3t0UqdOreoKFCgQwyvUk2TOnJkff/yRp55yaAiHDh3q8ZDOSSEkJESFaIhu9QQOqyEzXEJSE2+PHTsWcJifRrc227hxo1rBrV69mmPHjin9vWEYalVRuXJl7t69qyyZVq5cadlsHuCrr77i0qVLaq8gOWCaaJts2rQpWahkAZLq0ToaOAocBBYBaYBCwB/ASRzqnDSu9Iw9evSo8jD7/PPPZfXq1bJ69Wp5//33pWvXrnLo0CE5dOiQREVFqaxD5cuXt8QzbuzYscpzcMOGDZI6dWpJnTq1tGrVSmw2mwoPO3fuXMu9+KKXV199Ve7evSt9+/aVvn37iq+vr5QvX17Kly8vXbp0kWPHjsmff/4pf/75p+XZe+bMmSNRUVHKM9Xqa5eYUqVKFalSpYpcvHgxRsjjDBkySIYMGVzWV82aNVVI4riySMVW9+uvv0rnzp0tv1YVK1aUihUrSkREhCWhxuMrZ8+edfJuju5d7sbi/jDFIvIe8N5Dh08D1mZS0Gg0Gs2/WB3nJjGxbsaMGSNjxoyR8PBwuX//vlP55ptv5JtvvrEs9kr0MmfOHPXrvmzZMtm0aZNs2rRJHWvcuLE0btzYcjljK+as/u7du06zlJCQEHn//ffFx8dHfHx8LJOvTp06UqdOHQkLC5PQ0FBp2rSpNG3a1PLrlpRSqVIl+eeff+Sff/5RM/qaNWtKzZo1XdrPU089JaNHj5bRo0fHO6O/ePGiLF26VJYuXSqZM2e2/PqkTZtW9u7dK3v37pUzZ864dKWT1FKqVCm5du2aek5GjhwpD/Ye3V0SNKO3fJBPzED/pJQBAwbECFxmt9vl+vXrMnr0aEmXLp2kS5fOcjmftFKgQAG5ceOG3LhxQ8LCwqRFixaWy+Sq0r59e2nfvr3s3btXxowZ43LVzcOlS5cucvDgQTl48KBERkbKgQMHVALwZ555xvLrEb307dtXPUtlypSxXJ7opUePHnLv3j0l39ChQz3Vtx7orS5Zs2aVIUOGyJAhQyQ0NFQ2btwoGzdulDfffNNy2Z7EYv4wzpgxQ814ly1bZrlcunimHD58WPbt2yf79u1zSh+YXMq5c+dU2koP7gvq6JUajUajAePBjNpaIbQdvSYB9OvXD3Akldi+fTvg8Dz2dAIMjTX8888/ysPcijzKyZQ9IlLxUY30QK95IqhcubJKTP3ZZ5+ppNXJJdaORmMRCRrotepGo9FoUjh6Rq/RaDRPLnpGr9FoNBo90Gs0Gk2KRw/0Go1Gk8LRA71Go9GkcPRAr9FoNCkcPdBrNBpNCkcP9BqNRpPC+U8M9F999RWnT5+mSpUqVKlSxWpxNBqNxqMkKfHIk0JAQAAFChRQSaxLlSpFZGSkpTK9/PLLKu1hxYoVGTBgABs3bgQcLv5HjhwB8Hj6Q41Gk/L4T8zoNRqN5r9Mig6BkC9fPgBOnjxJ6tSp1fEMGTIQHh7uji7jJF26dBQvXhyAMWPGULt2bdKkSRNn+zNnzgCwYcMGhg4dyu3btwGw2WzuF/YJIF26dADUr1+f9957j3LlygHw8P3co0cPbt68qV6fPHmSgwcPulSWZs2a0b9/fwCef/55DMOIIceqVasA+OGHH/j5558ByJYtG8ePH+fOnTsulUeTfChZsiQAAwYMIE+ePAA0bNiQ1atXs23bNtVu7ty5gCNZ/GPimuiVhmF8DjQCropI6QfH/IBlQAHgLNBaRG4ahmEAU4EGwF2gq4g8UvfgroG+TJkyAPz555/Avw9by5Ytsdvt7ujSiaeffpoaNWoAjgGpYcOGiT7X6NGjAfj22285cOCAS+RLCvnz5wdg+/bt1K9fH8DlA6hJsWLFGDp0qNOx9OnTA9CqVavHOtehQ4do2bIlAMePH0+SXM2aNQNg4cKFZMiQIcHvO3HiBOD4DNevXyciIkLVDRw4EMBpENA8ufzvf/8D4I033oi3nTkZeffdd5kzZ87jdJGggT4hOvovgBnAwmjH3gbWi8g4wzDefvB6KPASUPRBqQLMevDX43h7e8cYHJYsWQLgkUEeoEaNGkybNi3WuvPnz8c5O/f391f6e5P33nsPgOvXr7t9oA8MDOTevXucP38+zjazZs0CICIigtDQULfK88svv/DUU0+55FylSpVi165dACxYsIDXX3890efKkSMHwGMN8gBFixZV/z/8uZYtWwZAixYtlJz/JTJkyKDu/UaNGqmVWkKYNm2aWgknF2KbiOzbt4+LFy86HatduzYAbdu2fdyBPkE8UkcvIr8DwQ8dbgosePD/AqBZtOMLxcEOIIthGP6uElaj0Wg0iSCBOV0LAAejvQ55qP7mg79rgWejHV8PVLQiZ+y0adNUMm6ztGrVSlq1auWxHJKvvvqqrFixQlasWCE2m00uXrwoFy9elNGjR0vGjBnjfN/rr7/ulFQ8eunXr5/b5G3evLk0b95cbt++HW9e22rVqsn9+/fl/v37Mnr0aLdfx4kTJ8a4DsHBwRIcHCxvvvmmHDlyJM7rFV/566+/pFSpUlKqVKlEyRUZGSmRkZGJ6vtR5f79+7Ju3TrJmjWrZM2a1WP3bFwlVapUUrRoUaeSJk0al5y7ffv2MnPmTJk5c6bs378/0dfs2LFjkjNnTsmZM6fl18ssFy5ckAsXLojNZpOjR4/K0aNHY8iXK1cu1S44OFi6du0qOXLkkBw5ciSkjwTljHW1eaURyzGJtaFh9AZ6u7h/evXqBTg24azmq6++Uiad7777Lvfu3QPg7Nmz8b4vtiV7WFgYANeuXXOtkNHo0KED4NjL+Pjjj+Ns17RpU7y9HbfOihUr3CaPyaeffqpURSZRUVGAQwW2cuVK3nrrLcChE/36668Bh1mtn59fnOfNkyePUr8kBlOd1rFjR3Vsx44dzJ8/X72uWLGiuicBihQpQqpUqR55bm9vb1588UXy5s0L4LSh/CiaNGnCmjVrEtw+Or6+vtSrVw+A7t274+PjA0Dq1KnVfpPJyJEjGTt2bKL6ic7ixYuVOtVut3Pu3DlVt3nzZnXPmybH0SldujQAr7/+OkWKFFH3cHz3r1WYz/DVq1cByJo1K+AYs8yNWnCYV5vPVevWrV3Sd2LNK6+YKpkHf68+OP43kC9au7zApdhOICJzRaRiQjYSNBqNRpN4EjujXwN0AcY9+Ls62vHXDMNYimMT9paIXE6ylAmkW7duTJ8+HQAfHx/lbBQUFOQpEZyIPgszzSPjInXq1Hz44YdA7Bs45sbyN99840IJnXnmmWcA1CokLvLkyYPDwMozVKxYkcWLF8dZf+7cOV577TX1uk2bNgB8+eWXVK5cOUZ707S2V69ebNq0KdFymd+X+Tc2Nm/e7DS77Nu3r7IYAnjnnXfIkiVLnO83LYQeZwP+l19+SXDbnDlzUrduXcBh3VSzZk2nmbv5DK1evZq0adNSqVIlJ9ldMaM/fvy4SvA+duxYtSJ7FPny5eO5555zOhZ9NZDcMM29n3vuOQ4ePMgPP/wAOO7v6NjtdmUh6DISoD9fAlwGInHM2HsA2XDo3088+Ov3oK0BzAROAQdIgH7+cXX0GTNmlGeeeUaeeeYZ6d69u8yaNUtmzZolN27ccNLH9+/fXwoXLiyFCxe2TEef0PL888/LmjVr4tQ9njhxQnLnzi25c+d2mwx58uSRmzdvys2bN2X8+PHxtg0LC5M7d+7InTt3JDAw0NJrlyFDBilYsKD88ccfqpi60Niu5a1bt6Rdu3bSrl07y793QDJlyqS+26VLl8qtW7fk1q1bSt7t27fL9u3b3db/4cOH1fMhIuqviMiKFSucdN7du3d3ajt79mzLrlvBggVl165dTt/typUrJVOmTJIpUybLv1ezRNfRJ6ScOnVKXnnllcfpI0E6+gRtxrq7PM6FK1GihHqQow/sN2/elDlz5qiNIm9vbylQoIAUKFAg2Q703bp1k27duklEREScX/x7770nZcuW9Yg8p0+fltOnT8uMGTPi3Wiz2+2yd+9e2bt3r6RKlUrSpEnjso25hJYMGTJIhgwZZMmSJQl+iIKDg6VFixaWf+8PFy8vL/Hy8pLevXtLaGiohIaGKpnbtGkjbdq0cVvfhw4dkrCwMAkLC5Nt27ZJw4YNJSAgQAICAsTLy0u1e+uttyQsLEw9S6dOnRJ/f3+PXqf06dNLkyZNpEmTJvLPP//E+H4Tu6nuzpKQgX7//v3qxz516tSP20eCBnodAkGj0WhSOE9cULMjR45QtmxZwNnx5Pbt2/E6+MDjO7a4i6effpqmTZsyYsQIgBhWGPfu3eP7778HHF6XnnICWb58OQCDBg0iR44cDBs2DIDTp0/HaFuoUCEAfv31V6WjfhzdcFIx9dqPY5UwcOBAVq5c6S6REo2vry9ADMuiHTt2qEB37mLQoPXhBF0AAB5kSURBVEGcPHkSQP2NTpMmTQCHZ3batGmVxVidOnW4fNlj228AjBo1ikGDBsVZP3PmTKdwErt37wbgiy++eKSlmzsoVqyYCtXxMHa7nd69HUaH3377bWJCHzweVqtt3GVHD0i2bNkkW7ZscunSJbHb7bJy5UpZuXKlJUu41KlTS4kSJaREiRJy7Ngxp6VbRESEWj6HhYXJoEGDLJHRtNleuXKlk6522bJlsmDBAlmwYIFs3rxZ6XLtdnu89vbuLH5+fuLn5yc7d+5MsOrm0qVLUr58eUvkjavkzJlTvv/+e/n++++dZI2MjJRevXpZKttbb72lVEl2u13OnTunVKNWyLNy5cpE2dcvW7ZMUqVK5REZvb29xdvbW+rWrStnz551kiMkJERCQkJk2bJl8vTTT7uqT6260Wg0Gk0Kj15psnXrVqpVq8aAAQMA4ow/406GDx+uApOZbN68GXDEN3l42W4lqVKlok2bNrz88ssAFCxYUNVlzZqVgIAAmjZtCjiiMZrOS1ZQpEgRKlSo4HRswQJHdI7oEUtNJk+ezJAhQzwiW1yYqrquXbvSo0cPp2Q4ZoCzCRMmKIcsT9O4cWPAocozr+Hp06fp37+/Mgm0gpIlS5ItW7ZY63LmzEmnTp2Us1pAQADjx48HHKbW27Zt4/nnnwdw2/1asGBB5Rz3cJwtQJkAu/hZT1BQM8vVNu5U3Zhl69atlljdZMiQQcqWLStly5aVkydPOi3jfv31V/H39/e45UJSS6dOncRut0uuXLkkV65clssTWzGtgGbPnh1jGR8REaHMc62Sr0ePHtKjR49Y1QwbN26UjRs3WiZbw4YN5fbt23L79m2x2+1y9uxZOXv2rGXqmkcV87vctGmT5M+f36kuKChIgoKClBlmyZIlpWTJkm6Ro0KFCnLu3Ll4VUh16tSROnXquLpvS0IgJFtERLkee4quXbvGunrYtGkTzZs3d3vUR3dgbsImZ0znm6+++orWrVuTOXNmVZcqVSqPOnuZmCEaXn31VbJnz+5UZzrTlS1bVsluBQ0bNmTVqlVqxXHq1CkVDiG5RYUEqFq1KuPGjQNgyJAhMYwxTGevxYsXExQUpIwFXBUJFVDRNVetWuUUxsBms7F27Vq18rUaraPXaDSaFE6KmdEXLVpUBQkCh5v7jRs3AEeAo/Hjx6sAVjly5CB9+vSMGTMGcOgiExsEKi5KlCgRQxe8fv16wBEEK77ZfEBAgDIFHTt2LAEBAU71pgnZsGHDPJagwsyG1bhxYw4ePPjIkA7JgfPnz8fIDTxnzhz++OMPj8lQvXp13njjDRV8y3SDN9mxYwcTJkwAeKR5sLswdfKLFy92MvVt3LhxspzJmwwePFiZLx49ejTOdn/88QeRkZHkzp3b5TKYoTnM2bxpFj1p0iTKlSuXbGb0T+RAb0bUK1y4MD179gTglVdecYohEhERoQZEM4KhGUPj2rVr+Pj4qCX9P//847KB3lzKff311yryoIlpp1y0aFEnNdKoUaOcHrAOHTrEGNyj061bN8CzWYjMH9Hy5cszfvx4j6difBxM/4qvvvoqhprkzp07Thmd3E3evHlVvBoTM4rhrFmz+Oijj9xvQx0P+fLlU/FqMmbMyNWrV3n11VcBOHbsmGVyJYTs2bNTvnx5wJFU6MMPP+T3339X9WbMqKZNm8a6MZ9UOnbsSGBgIOCw2a9Tp46KUBsZGckrr7zi8j4TyxM30OfKlYspU6YA/wavio7pxCEiHDp0CPg3lWBcLFy4MN76x8EczDds2EDhwoWd6swvvnXr1k4z4vz58z+W3vjhHxBPED0NoidCE9euXZupU6c6HevTpw8AV65cUcdu3bpF6tSp1Y/8oEGDVGrD+H4sPcX7778f49i8efMAWLdunRoYPI35A7h161Z1P50/f54uXbrw22+/WSLT47J//34VgK1u3bo888wzXL9+XdWbunhzEuWq0OXFihUDHN+tl5dD+22z2WKs0lu0aKH+v3r1qsf3CKOjdfQajUaTwnniZvTt27ePdSa/bt06Jk+ezNatWwFi6GY9hakueuONN8icOXOsLvpZs2Z12k9ICKZNdXBwMJ999lnSBX1Mood63rNnj9v78/X1pWTJkk7Hoi/LTbZt20b27NnVEjo+vvjiC+W74G5MVUGuXLli1Jn+HAMGDODbb7/l7t27Mdp8/vnn7NixwzQ/drk1TqlSpQDH6tC0K2/fvv0TlZR86NChKr9sr169SJ8+vUpa/zDz5s17ZPjthGKqfKMnrTF18+Z92KVLF5WcBxxhDv766y+X9J8YnjiHqQIFCrB6tSP8/aVLl5TePXpWn+RCo0aN1ENtOmvEx4ULFwBo164dhw8fdqozl4WeSmwenbJly7Jv3z7AsdR/ONOQO2jWrJlLVEQhISHqmtWoUSPeTTtX0qyZI43ykiVL1J5SYjA3jt999102bNjgEtkAFS9q69at/Pzzz4CzquFJwby2GTNmpE+fPrE6VP3xxx98/fXXuGqsM40sPvroI3UsJCSEbdu2qU138wfHVIM1a9bMXQYMCXKY0qobjUajSeE8cTP6J42MGTMCDlO1AgUKACgrh7lz5wL/qiTMKJE7d+70sJTxU6tWLWUaOm3aNN58802391m+fHll/dGhQ4fHmhWbm+s2m43BgwdbatXSpUsX+vTpozY8ozvVxMeZM2dInz69WuUVKlQoSflt4yJ//vzq+jwJJrPJAXPW/vPPP8eqmjMJDw+nbdu2AKxdu9Zd4iRoRq8Hes0jmTBhAl27dgUcJq2e9ugdPHiwilsSG6NHj2b//v3q9bp16wDHQJ9cMNPFmSoTcFgyPWxnbYauXrFiBX5+fpw6dQpwTBSs2JvRxE2ZMmWU2itnzpxOdT/99BOTJk1yqbotDvRAr3ENEyZMoHr16gA8++yzFkuj0Wii4RodvWEYnxuGcdUwjIPRjk00DOOoYRh/GYbxrWEYWaLVDTMM46RhGMcMw6ifePk1Go1G4woeOaM3DOM54A6wUERKPzhWD9ggIlGGYYwHEJGhhmGUxJFMvDKQB/gVCBSReNfQekav0Wg0icI1M3oR+R0IfujYzyJiBnXeAZiumk2BpSJyX0TOACdxDPoajUajsQhXmFd2B8xsBE8BF6LV/f3gmEaj0WgsIkmesYZhvAtEAYvNQ7E0i1UtYxhGb6B3UvrXaDQazaNJ9EBvGEYXoBHwgvyr6P8biB6HNS9wKbb3i8hcYO7/2zv36Cira4H/Tngk64Y3CTGBYIFGRQtKCpEoL12FNjxEyqsavfKoYHmUlKAXSlrBqkuWJAExEUQsDZFCIFyNFhssZSmUcDWgWAHFgARCeIi3QrgFjJlz//jmHGYyMxBgZr5kcn5rnTUz3yPfzp7v23POPvvs7fxbxkdvMBgMAeK6XDdCiJ8B/wU8IKV0TdRRBPxCCBEuhOgCJADBS/5tMBgMBg+u2qMXQvwZGARECSEqgKeBeUA48J4zve4uKeUTUsp9QogCYD+WS2f61SJuDAaDwRBYzIIpg8FgaLiYpGaKZ599Fikl5eXllJeX6/wzBoPB0BhoFIbeYDAYGjMNrvBIXWnSpIlOEDV79mz++te/6qyQXbt2tbUIgKFxo0odzp07l/fee4/S0lIAnSDLYPA7UkrbG1asvV9bamqqdDgc0uFwyOeff97vf9800663LVu2TC5btkzW1NRIh8Mht23bJrdt2yYjIyNtl612S0lJkSkpKbKmpkYeO3ZMf46OjrZdtobQwsLCZFhYmFywYIFUzJ0715/XKK2TjbXbyPvb0CclJcmkpCT59ddfy9LSUllaWirDw8Nt/8IbeuvTp4/s06ePlFLKmpoat5aRkSEzMjJsl7EhtKFDh8pz587Jc+fOaUOv9BgXF2e7fLWbMuzV1dVubdOmTbbLVt9bly5dZF5enszLy3N7Xt5++21/XqdOht746A0GgyHECTkf/RNPPAFYBbgzMzMB/xdWboyo4uQOh8Ojbq3aFx0dTWFhodci3vWF+Ph4Xcc3OTmZ5ORkAEpKSnTO/UDQrVs3AN544w0iIyPd9r3xhpVB5PTp0wG7/rXQpo2VdTw3N5cBAwZ4PUbNKxh8k5mZ6VZYprq6GrhcSDyYhJShHzhwII8++igA69ev54UXXrBZorrRo0cP7rnnHl555RW9zbkQjeLiYpYuXcq7777r6/SAoUofFhcXc9NNN131+BkzZnDw4MF6ZejHjRvH3XffDbgb9tpUVFQEVI5Zs2YB0KpVK7ftW7du5Q9/+AMA33//vcd5dtCzZ08AkpKSiI2NBTyL0i9cuJB9+/bx1ltv+fXaERERbiUT7733Xv0sJCQk+Dyvurqa/Px8KiutjCt2Vxe75ZZbtB4V8+bNA3B7zoNFSBn6H/3oRzRp0gSA48eP2yzNlRk7dixjxowBYPjw4URERLhVqVfvBw8eTM+ePZk/fz4Af/zjH4MmY7NmzQArSqmh4NpjHzt2LPHx8R7HlJSUALBkyRIKCgoCLtOLL77I+PHjve4bPHhwwK9/rbRt2xbAY+RRmxUrVugfgLffftsv1169ejXjxo27rnOfe+45ioqKAJg8eTLffPONX2S6FlQN2c2bN9OlSxe3fXaOgoyP3mAwGEKckOrRDxs2jBMnTgDw+uuv2yyNb1JTU8nKyiIqKgqANWvWUFxcTPPmzQFYtGiR2/A1JiaGTp06ef1bgWThwoU+9z3++OOAVfR66tSpwRLpqmRmZjJ27Fj9ecOGDWzcuFF/DkYPvjZ33XWX/q7hshtk2bJlQZflSixdupQZM2a4bQsL890XjImJ4eabb/arDOHh4ezZswe4trm1Jk2akJSUxAMPPABA3759dZH4YNK6dWsAj978zp07OXjwYNDlUYSMoW/bti2JiYmsWrUKgM8//9xmiXwzYcIEoqKimDt3LgAvvfQSly5d0oa+e/fuTJs2DbCGzw6Hg6qqqqDJl5KSwjvvvON133PPPcfvf/97/blVq1baGISFhWl/arBRE+99+/Zlw4YNAGRnZ2s3jV3ccccd3H777W7bVq9eDVgL+eoTUkoPX7xiw4YNbN++XU/O/vznPwdg1KhRAKxbt44zZ87csAypqal60lK91oWmTZuydetW+vfvD8CgQYNsMfRKL4qdO3cCMGbMGE6dOhV0eRTGdWMwGAwhTsj06OfMmUOHDh0CHj3hD1q2bAlcjrKorq5m+PDhegL2nnvucZsIy8/PZ8mSJUGV0VfPzrU3D569QDuyoY4bN073jktKSkhPTwfg2LFjVzotKEyZMsUjYumTTz6xSRpPOnbsqHvpjzzyCADffvstAGfOnGH37t0ATJ8+nQsXLtChQwe389W5rVu39kuP/t///vfVD/JCZGSk7s2DPS46sEbjisOHD2s3Yu3efJs2bVi0aBEAJ06c0EEW5eXlAZErZAz96NGjAfSse31GfenKaKanp3P+/Hmv4WNvvvkm06dPD6p83nzzKmxNodxMrr5nu1BRNmAZehVC2alTJ9tdN3379rX1+r648847AasToVxL6gd7zZo1gKdrKSEhgd/+9rdBlLJhERMTw/33368/r1ixgpMnT3ocl5SURE5ODomJiXrbhAkTAOjVqxf/+te//C5bgzf07dq1A6xf9B07dnhVrDc6d+7M0aNHAymaT6ZNm0ZMTAx9+vQBPGOrXSkuLr7uXs71UlpaSq9evdy2TZniXt535syZALY/+AUFBSQnJ2uDfvz4cRYvXgzArl27qKiosKVnf+uttwIQFxfnMW+hQipbtWqFEELL+9133wVNPtUTv+222+p8TmVlJStWrADwmIBfuHChHhHYwYMPPmjbtRW//OUviYuLA6yRiUqiqFBhq7/73e/cjDygw4DDw8MDIpvx0RsMBkOI0+B79KpH0rFjR5YsWeJzRVxsbKweirZp04bx48fr8K2cnBwWLFgQFHnB6nUOGDCAiIgIwFoFO378eLcVc3l5eUBwF0gppk6d6uZ3Lyoq0r5aRe0efjBJTk7WUTbJycmkp6frSJtjx47RsWNHwHI9KH99sFHukLi4OI95ixEjRuhXIYSOvtq9e7eO2gjE8F0RGRmpfclhYWE6amr//v0MGTJEhyh7wzXCKiwsjP379wPw5JNPBkzeuqB6y2qU/uWXX+p9w4YN089abQoLC/0mg1pJDvDVV1+xfft2t/1Lly4FYOjQocDlhXt79+7VqVsCRYM39K74CqkcPXo0mZmZ2gCsXr2atWvXamM1evTooBp6sIbpaqjevXt3N3/o+++/z5w5c4BrCzG7UVQODvUwq4dFzX+4otwRrnHWxcXF5OTkBFpMt1QG6enpZGVlBfyagURNvA8YMEDfk6tWrfLL5KY3Fi5cqNdBuP6gL1++/IpGPi4uzu28/fv3k5qaCnDF8wJB9+7diYiI0K5b9UOj1pu4Tmq2aNECIQTnz58H4OOPP2bTpk1+lScqKkr/gIP7D42ic+fO+v3Jkyd5+OGHASsUNNCG/qquGyHE60KI00KIz7zsmyOEkEKIKOdnIYR4SQhRJoT4VAiR6PkXDQaDwRBM6tKjXw28DOS5bhRCxAODAdcZzRQgwdnuBl5xvgaF2nlN1DB47dq1HDlyhPvuuw+AHTt2kJCQoHtPKntgsFG/8GlpaSQkJOje+5NPPhn0PB0DBw7UE4gqQ6WvUMkxY8bonpRrjzBYyZqysrLcXDW1cV0ZaxcqSunbb7/V2SAVe/fuBazhvVpwpHj++ecB+MlPfsLDDz/M119/7XfZak8EKqKjo2nWrFmdR5ETJkwIaKW2yMhIBg4cyC233AJYOlGj8ttuu+2KE5dSSp3vasuWLWzevJnDhw8DcOjQIb/L2qxZM7fV7OvXr3fb/+Mf/1jr/eTJk4wZM0a7mSZPnux3eWpzVUMvpfxACPEDL7uygacA1/R1I4E8aVmIXUKINkKIWCllUMZ1R44c0e/btWunDfjatWuZNWsW586dA6yZ7fz8fH1sbf9zsBg0aBBgzdZLKbX7SN2QwaRnz55uQ0tfREZGMnz4cL3UGy6nQ/BXYqu64CuSZvbs2foHPysry7ZYehVxUVZWRu/evd32Kd9tdnY2L7zwAitXrgSsLKaK+++/n1GjRvHqq6/6Xba//OUvbjHnin79+nnEwyu/84gRI9xCaYuKiujdu3dAnh11nX379rkZT2+o71d95ypR4Jtvvul3ua6FqVOnUlBQoP+XRYsWaRddbm4uJSUl2j2blJSkn/mLFy8GRJ7r8tELIR4Ajksp99YKHesIuD5ZFc5tATP0Kib97NmzDBs2jOLiYgCqqqq0MS8sLOTcuXM0bWr9u1u2bKF3795kZGQAlk882Nx555289tprgNX7WL58eb1Lq+xtTcKLL76o/bKKQPlnVRbDuix+UXMcaWlp+uEP9iIzV/r16wdcDrN0ReWTSUlJ4ZFHHvGZNmLixIkBMfS1J9JVVsVJkyZ5zAuoSdva8yCvvvqqftb8jZq7Onv2LNHR0Xph0ZkzZ3SMvwq6UMazpKSE2NhYjwnQYHHhwgXKysr44Q9/CFiLHgcPHqxTlyhvAljG/Nlnn9WBAhUVFXqCVi1W8zfXbOiFEP8BzAeGeNvtZZvX8b8QYgpgX+iGwWAwNBbqWNP1B8Bnzvc9gNPAEWf7HstPfxOwAnjI5bwvgNhg1Iz9/PPPpcPhkBMnTpQTJ05029eyZUuZkpIiy8vLZXl5uXQ4HHLevHm21ZKMjY2Vmzdv1jUkP/roI9vrW86cOVPXA62pqZHV1dWyW7duslu3bhKQzzzzjHzmmWf0PtUKCwtlVFSUjIqK8rtMip07d8px48Z5PSYzM1O6UlBQIOPj42V8fLyt+oyLi5NxcXFyx44dHjV2XZu3Grw1NTWyqqpKTpo0KSCyrV271kMGKaXMycmRKSkpMjc3V+bm5rrtU8dmZ2fL7OzsoOhw6NChMjU1VUZERMiIiIgrHvvxxx9Lh8Mh27dvL9u3b2/Ld75y5Uo3vZ46dUpWVlbKysrKK94DaWlpN3Jd/xUHx8XQe9l3BIhyvh8GvIvVs+8LfBis4uAjR46U58+flxcvXpQXL16UR48e1e3EiRPS4XDIqqoqWVVVJdPT02Xz5s1tuRkAuWfPHllTUyPLyspkWVmZTExMtE0W1X796197GJ9JkybJSZMm6YfedV9+fr7Mz88PqEzq+5NSyqNHj8qCggJZUFCgt7n+ENQH4+6txcbGyn/84x/XbOg3btwYMJkWL17s9mOtrlm7AHjtfZWVlbJ///6yf//+tuu1dqsPhr5z587ywIED8sCBA1c07KrNnz9fzp8/XzZt2vRGruuf4uBCiD8DJcCtQogKIcSVpog3A4eBMmAlMO1qf99gMBgMgUVIG7INegghhF+EmD17NgMHDgRwW7zwzTffUFhYqAs97Nu3zx+XuyaaNGnCQw89BFgLti5evMhTTz0FWLPwdjNz5kw94RYWFuYze6Xap1YkByJUTaEmY9PS0txqvW7YsEFPuG7cuNH2xGVXIyYmhl/96lcA/OY3v6FFixZ6X21df/jhhwCMHDkyYMXC//73v7tF3ahFb96+8wsXLgDWau4JEyZ45G+pL7z//vsMGDBAR7nYUUYQLodMz5o1ixEjRuii8IAOCf7ggw9Yt24dZ8+eBW64vu1uKWXvqx0UUoa+PpOYmKhDvlq3bk1GRka9qjD06KOP8vLLLwPWSkJfhv7QoUMsX75cr4AN5srdUKBly5Y6G+mQIUO4dOmSTlv9zjvv6FDLQBqq6Oho1q1bB1ircX0Z+qKiIrZs2QKgk5nVV9LS0sjKytLhmHYZele6dOmi9de1a1cdTu3nyCBj6OsDque7fft2vcho8eLFZGdn1znTZrBQGQlzc3N9GnpVMNzQsFElANevX6+zqDocDh5//HEdLrt79+6ApWHwN/XR0AO6UluLFi10CmNfz9Z1UidDb7JXGgwGQ4hjevQBpHXr1noxVo8ePfT7xx57rF5UP/JFRkYGTz/9tF7G77rAJlCLZAyGG6Fdu3Z0796dXbt2ATfs925IGNeNnXTu3Jm//e1vuvRaXl6eXvIczAITBoMhpDGG3g5UmoXXXnuN8PBwnU9eTcoYDAaDHzE+eoPBYDCYHr3BYDA0ZEyP3mAwGAzG0BsMBkPIYwy9wWAwhDj1pTj4GeD/nK+Gy0RhdFIboxNPjE680xj0cnNdDqoXk7EAQojSukwqNCaMTjwxOvHE6MQ7Ri+XMa4bg8FgCHGMoTcYDIYQpz4Zev9XQW74GJ14YnTiidGJd4xenNQbH73BYDAYAkN96tEbDAaDIQDYbuiFED8TQnwhhCgTQsy1Wx47EUIcEUL8UwjxiRCi1LmtnRDiPSHEl87XtnbLGUiEEK8LIU4LIT5z2eZVB8LiJee986kQItE+yQOHD50sEEIcd94rnwghhrrsm+fUyRdCiJ/aI3VgEULECyG2CSEOCCH2CSFmObc36nvFF7YaeiFEEyAHSAFuBx4SQtxup0z1gPuklHe5hIXNBbZKKROArc7Pocxq4Ge1tvnSQQqQ4GxTgFeCJGOwWY2nTgCynffKXVLKzQDO5+cXwB3Oc3Kdz1mo8T2QLqXsDvQFpjv/98Z+r3jF7h59ElAmpTwspfwOWAeMtFmm+sZI4E/O938CHrRRloAjpfwA+N9am33pYCSQJy12AW2EELHBkTR4+NCJL0YC66SUl6SUXwFlWM9ZSCGlPCGl3ON8XwUcADrSyO8VX9ht6DsCrqWWKpzbGisS2CKE2C2EUGWdYqSUJ8C6uYEOtklnH7500NjvnxlON8TrLi69RqcTIcQPgF7A/2DuFa/YbeiFl22NOQzoXillItYwc7oQYoDdAtVzGvP98wrQDbgLOAFkOrc3Kp0IIVoAhUCalPLclQ71si1k9VIbuw19BRDv8rkTUGmTLLYjpax0vp4G/htryH1KDTGdr6ftk9A2fOmg0d4/UspTUsoaKaUDWMll90yj0YkQohmWkX9DSrnJudncK16w29B/BCQIIboIIZpjTSIV2SyTLQghIoUQLdV7YAjwGZY+HnMe9hjwlj0S2oovHRQB/+mMqOgLnFXD9lCnln95FNa9ApZOfiGECBdCdMGafPww2PIFGiGEAFYBB6SUWS67zL3iDSmlrQ0YChwEDgHz7ZbHRj10BfY62z6lC6A9VvTAl87XdnbLGmA9/BnLFVGN1Qub7EsHWMPxHOe980+gt93yB1Ena5z/86dYRizW5fj5Tp18AaTYLX+AdNIPy/XyKfCJsw1t7PeKr2ZWxhoMBkOIY7frxmAwGAwBxhh6g8FgCHGMoTcYDIYQxxh6g8FgCHGMoTcYDIYQxxh6g8FgCHGMoTcYDIYQxxh6g8FgCHH+H82AiL7Fd4qvAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "from torchvision import utils\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "%matplotlib inline\n",
    "\n",
    "# First, add a dimension to tensor to become B*C*H*W\n",
    "if len(x_train.shape)==3:\n",
    "    x_train=x_train.unsqueeze(1)\n",
    "print(x_train.shape)\n",
    "\n",
    "if len(x_val.shape)==3:\n",
    "    x_val=x_val.unsqueeze(1)\n",
    "\n",
    "# make a grid of 40 images, 8 images per row\n",
    "x_grid=utils.make_grid(x_train[:40], nrow=8, padding=2)\n",
    "print(x_grid.shape)\n",
    "\n",
    "# helper function to display images\n",
    "def show(img):\n",
    "    # convert tensor to numpy array\n",
    "    npimg = img.numpy()\n",
    "    \n",
    "    # Convert to H*W*C shape\n",
    "    npimg_tr=np.transpose(npimg, (1,2,0))\n",
    "    \n",
    "    # display images\n",
    "    plt.imshow(npimg_tr,interpolation='nearest')\n",
    "\n",
    "# call helper function\n",
    "show(x_grid)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transform Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torchvision import transforms\n",
    "\n",
    "# loading MNIST training dataset\n",
    "train_data=datasets.MNIST(path2data, train=True, download=True)\n",
    "\n",
    "# define transformations\n",
    "data_transform = transforms.Compose([transforms.RandomHorizontalFlip(p=1),\n",
    "                                    transforms.RandomVerticalFlip(p=1),\n",
    "                                    transforms.ToTensor(),\n",
    "                                    ])\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Text(0.5, 1.0, 'transformed')"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAADHCAYAAAAJSqg8AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAFq1JREFUeJzt3XuUVfV5xvHvg4hR8ALJEqdEQrXWiDZiSiArcVVMRKM1IolJQ5vLUhJJK41NlNZi2mqMhlZjqwvaQJZGyYWYRvFCTTFRlKS2RGLUKGij1Cg4Qg1y9UKQt3+cjT2y9zBn5uxz+/F81po1Z97zO3u/e+Y97+yzr4oIzMys8w1odQJmZlYON3Qzs0S4oZuZJcIN3cwsEW7oZmaJcEM3M0uEG3oLSPqapL8pe2wv0xklKSQNrHdaZrWStK+kOyRtlPSvrc5nV5LulfTpVudRFr+5WyAiPtuIsWY9kfQ08OmI+FGTZ30WMBx4c0Rsb/K89zheQ28ySXu1Ogezag3+1PY24L/708z9abLv3NBLIumo7OPbBkmPSToji98g6V8k3SlpK3BiFvty1Wv/UlK3pOckfTrbNPI7Va//cvZ4gqTVki6QtC57zdlV0/lDST+XtEnSs5Iuae5vwdqRpG8CI4E7JG3J6i0kTZX0DHBPNu5fJT2fbR5ZKunoqmncIGmOpH+TtFnSMkmHZ89J0j9mNblR0iOSjpF0KfC3wB9l850qaYCkL0r6VTZ+vqQDs+ns3Cz4el5VsbOzmn5R0mclvSubzwZJs3dZ3nMkrczGLpb0tqrnJkp6PMtzNqAG//qbKyL8VecXsDfwJDATGAS8D9gMHAncAGwE3kvlH+ibstiXs9d+AHgeOBrYD/gmEMDvZM9Xj50AbAe+lM3zNOAlYGjV87+XzecdwFrgzOy5Udl0B7b69+WvltTo08BJu9TCfGAwsG8WPwfYH9gH+CfgoarX3wCsB8ZR2VT7beC72XOnAD8DDqLSII8CurLnLgG+VTWdc7L3ymHAEOAW4Js95VUV+1r23jkZeAW4FTgYGAGsA07IpnFmNv2jsjy/CNyfPfcWYBOVzUB7A5/P3k+fbvXfp6wvr6GX491UinNWRGyLiHuARcCU7PnbIuI/ImJHRLyyy2s/CnwjIh6LiJeAS3uZ12+AL0XEbyLiTmALlX8cRMS9EfGLbD6PAAuAE8pZREvQJRGxNSJeBoiI6yNic0S8SqURH7tz7TlzS0T8NCqbT74NjMniv6Hyj+DtgCJiZUR09zDPPwGujohVEbEF+GvgY7tsXnlDXpnLIuKViLgL2AosiIh1EbEG+DFwXDZuGvCVLIftwBXAmGwt/TRgRUR8PyJ+Q+Wf1vN9/aW1Mzf0cvwW8GxE7KiK/YrK2gPAs729turn3Y0F+HW8cXvkS1T+mSBpvKQlkv5X0kbgs1TWSsyKvF5rkvaSNEvSU5I2UVmjhzfWT3Xze73ushWY2cAcYK2keZIO6GGev0XlvbHTr6isSQ8vyqvK2qrHLxf8PCR7/DbgmmxTzAYqnypE5b34hvdaVFbbe3u/dRQ39HI8Bxwqqfr3ORJYkz3e3SUtu4G3Vv18aB15fAe4HTg0Ig6k8jE1rW2E1l9FNVgd+2NgEnAScCCVTR1QY/1ExLUR8ftUNh3+LjCjh6HPUWm6O42kstmjukHXcwnYZ4FpEXFQ1de+EXE/lffa6+8vSaK+91vbcUMvxzIqHwP/UtLekiYAHwS+W8Nrvwecne1U3Y/KTqT+2h9YHxGvSBpH5U1qBpWGedhunt8feBX4NZV9OVfUOuFsB+V4SXtTeR+8ArzWw/AFwOcl/bakIdl8boryDmn8GvDXO3foSjpQ0key5/4NOFrSh7JNPJ8DDilpvm3BDb0EEbENOAM4FXgB+GfgkxHxeA2v/QFwLbCEys6c/8yeerUfqfwZ8CVJm6n8Y/heP6ZhafoK8MVsM8RZBc/Pp7L5Yw2wAvivPkz7AODrwIvZNH4NXNXD2Oup7PhfCvwPleb/532Y125FxELg74HvZpuOHqXyviQiXgA+AszKcjwC+I+y5t0OlO39tTYh6SgqRbhPiWstZrYH8Bp6G5A0WdIgSUOprF3c4WZuZn3lht4epgH/CzxFZdvjn7Y2HTPrRN7kYmaWCK+hm5kloq6GLukDkp6Q9KSki8pKyqzVXNvWifq9yUWVqwb+NzARWA08AEyJiBW7eY2371hDRUTdJ1K5tq0d1VLb9ayhjwOezK7JsI3KSTST6pieWbtwbVtHqqehj+CN10FYzf9fu+R1ks6VtFzS8jrmZdZMrm3rSPVcQL5o9T/3sTMi5gHzwB9LrWO4tq0j1bOGvpo3XtjmrVQuvGPW6Vzb1pHqaegPAEdkF9kZBHyMypX+zDqda9s6Ur83uUTEdknTgcXAXsD1EfFYaZmZtYhr2zpVU88U9XZGa7QyDlvsD9e2NVqjD1s0M7M24oZuZpYIN3Qzs0S4oZuZJcIN3cwsEW7oZmaJcEM3M0tEPddyMTNruIMOOqgwfsEFF+RiAwcWt7TTTz89Fxs9enR9iQFXXnllLnbRRa27fL7X0M3MEuGGbmaWCDd0M7NEuKGbmSXCDd3MLBE+ysXMmm7w4MGF8ZkzZ+Zin/vc5wrH7rfffqXmtNOaNWtysZ/+9KeFYxcuXNiQHPrLa+hmZolwQzczS4QbuplZItzQzcwSUddOUUlPA5uB14DtETG2jKRSt9dee+ViBx54YF3TnD59emG8aMfRkUceWTj2vPPOy8WuuuqqwrFTpkzJxV555ZXCsbNmzcrFLr300sKx7cK13T/77rtvLnbKKafkYkWn7QO85z3vKT0ngFWrVuViDz30UOHYadOm5WLr168vPadGKOMolxMj4oUSpmPWblzb1lG8ycXMLBH1NvQA7pL0M0nnlpGQWZtwbVvHqXeTy3sj4jlJBwM/lPR4RCytHpC9GfyGsE7j2raOU9caekQ8l31fBywExhWMmRcRY71TyTqJa9s6Ub/X0CUNBgZExObs8cnAl0rLrA2MHDkyFxs0aFDh2KK988cff3zh2KIL9n/4wx/uY3b9t3r16sL4tddem4tNnjy5cOzmzZtzsYcffrhw7H333deH7FpvT6jtvhgwIL/eN2nSpMKxM2bMyMXGjx9fek4Ajz/+eC7W09FeK1asyMXWrl1bek6tVs8ml+HAQkk7p/OdiPj3UrIyay3XtnWkfjf0iFgFHFtiLmZtwbVtncqHLZqZJcIN3cwsEYqI5s1Mat7M+mDMmDGF8XvuuScXq/cU/WbbsWNHLnbOOecUjt2yZUvN0+3u7s7FXnzxxcKxTzzxRM3TrVdEqGkzq9Kutd0XZ5xxRmH84osvzsXGjm3MgT1PP/10LjZ79uzCsQsWLMjFnn/++bJTahu11LbX0M3MEuGGbmaWCDd0M7NEuKGbmSXCDd3MLBE+ygUYNmxYYXzZsmW52GGHHdbodHY7f4ANGzbkYieeeGLh2G3btuVinXakTl/4KJfa3HnnnbnYSSedVDi26IYsfVF05Mktt9xSOLboiJZmHiXVznyUi5nZHsQN3cwsEW7oZmaJcEM3M0tEGTeJ7ng93dG76NrOp59+euHYn//857lY0fXFe1J0B/KJEycWjt26dWsudvTRRxeOPf/882vOwdJTdC1zgK6urlysLzs/77rrrsL4Aw88kIt9//vfz8UeeeSRmudltfMauplZItzQzcwS4YZuZpYIN3Qzs0T02tAlXS9pnaRHq2LDJP1Q0i+z70Mbm6ZZ+VzblppeT/2X9AfAFmB+RByTxf4BWB8RsyRdBAyNiL/qdWYddnp0kQMOOKAwvnnz5lxs7ty5hWOnTp2ai3384x/PxYou4G+715dT//eE2p4wYUJhfPHixbnYwIHFB71t3749F9tnn33qysv6rpRT/yNiKbDrcX2TgBuzxzcCZ/Y5O7MWc21bavq7DX14RHQDZN8PLi8ls5ZybVvHaviJRZLOBc5t9HzMms21be2mv2voayV1AWTf1/U0MCLmRcTYiGjMXWXNyuXato7V3zX024FPAbOy77eVllGb27RpU81jN27cWPPYz3zmM7nYTTfdVDh2x44dNU/X+iyp2r733nsL40uXLs3F3ve+9xWOLbokwCc+8YnCsXfccUcuVnT9fmuMWg5bXAD8J3CkpNWSplIp9omSfglMzH426yiubUtNr2voETGlh6feX3IuZk3l2rbU+ExRM7NEuKGbmSXCDd3MLBG9nvpf6sza9PToRhk8eHBhvOhIgBNOOCEXO/XUUwtf39PNBaxvp/6XqdNqe8SIEbnYxRdfXDh22rRpNU/37rvvzsW+8IUv5GKPPvpoLma7V8qp/2Zm1hnc0M3MEuGGbmaWCDd0M7NEeKdoCxx++OG52IMPPpiL9XTK9JIlS3Kx5cuXF46dM2dOLtbMv3mzeado/3V1dRXGP/nJT+ZiV1xxRc3TLbpXwNlnn104duHChTVPd0/jnaJmZnsQN3Qzs0S4oZuZJcIN3cwsEd4p2iYmT56ci33jG98oHLv//vvXPN2ZM2fmYvPnzy8c293dXfN025V3ipZPyv9KzzvvvMKxY8fm7/Vx4okn5mIHHXRQ4evf+c535mJPPfVUbynuEbxT1MxsD+KGbmaWCDd0M7NEuKGbmSWilnuKXi9pnaRHq2KXSFoj6aHs67TGpmlWPte2pabXo1wk/QGwBZgfEcdksUuALRFxVZ9mlvCRAI1wzDHHFMavvvrqXOz976/9Nphz584tjF9++eW52Jo1a2qebjvoy1Euru3mOPnkk3OxH/zgB4Vj77vvvlzsgx/8YOHYrVu31pdYhynlKJeIWAqsLyUjszbi2rbU1LMNfbqkR7KPrUNLy8is9Vzb1pH629D/BTgcGAN0A1/taaCkcyUtl1R8OUCz9uLato7Vr4YeEWsj4rWI2AF8HRi3m7HzImJsRORPITNrM65t62Q1nfovaRSwqGrHUVdEdGePPw+Mj4iP1TAd7zgqQdFp0z3tOCq6fEDRqdwA99xzTy42ceLEPmbXWn099d+13XgDBuTXG3/0ox8Vji26WXrRQQAAM2bMqC+xDlNLbQ/sbYCkBcAE4C2SVgN/B0yQNAYI4Gmg9tuCm7UJ17alpteGHhFTCsLXNSAXs6ZybVtqfKaomVki3NDNzBLhhm5mlgjf4CJxr776ai42cGDxrpPt27fnYqecckrh2HvvvbeuvBrFN7joDFOmFO2+gG9961u52HXXFe/WmDYtv7+6p5u/bNq0qQ/ZtSff4MLMbA/ihm5mlgg3dDOzRLihm5klotcTi6x13vGOdxTGzzrrrFzsXe96V+HYnnaAFlmxYkUutnTp0ppfb2maN29eLnbNNdcUjn3sscdyscGDB+diF154Yc3znzp1amH84YcfzsWOO+64wrGLFi3KxW699daac+gUXkM3M0uEG7qZWSLc0M3MEuGGbmaWCDd0M7NE+NT/FjjyyCNzsenTp+diH/rQhwpff8ghh9Q1/9dee60wXnTTgdNOO62ueTWbT/0v38aNG3OxQYMGFY5dsmRJLlZ0Q5bx48fXn1gfrFq1quYc1q9vz/uG+9R/M7M9iBu6mVki3NDNzBLhhm5mlohabhJ9KDAfOATYAcyLiGskDQNuAkZRuZnuRyPixcal2t6KdlT2dM3noh2go0aNKjslAJYvX56LXX755YVjb7/99obk0K46tbaLdqoDDB06NBfr6uqqebpDhgwpjO+333652IABxeuCPV0/v9Xe/OY352IjR44sHNuuO0VrUcsa+nbggog4Cng3cJ6k0cBFwN0RcQRwd/azWSdxbVtSem3oEdEdEQ9mjzcDK4ERwCTgxmzYjcCZjUrSrBFc25aaPl1tUdIo4DhgGTA8Irqh8saQdHAPrzkXOLe+NM0ay7VtKai5oUsaAtwM/EVEbJJqO38jIuYB87JpJHvyhXUu17aloqajXCTtTaXgvx0Rt2ThtZK6sue7gHWNSdGscVzblpJajnIRcB2wMiKurnrqduBTwKzs+20NybCFhg8fnouNHj26cOzs2bNzsbe//e2l5wSwbNmyXOzKK68sHHvbbfk/y44dO0rPqRN1Qm1PmDAhF7vssssKxx577LG5WNHNJQAWLlyYi/V0k5SejmhphJtvvjkX27BhQ+HYn/zkJ7nY/fffXzh227ZtudgzzzzTx+zaXy2bXN4LfAL4haSHsthMKsX+PUlTgWeAjzQmRbOGcW1bUnpt6BHxE6CnjYrvLzcds+ZxbVtqfKaomVki3NDNzBLRp+PQUzBs2LBcbO7cuYVjx4wZk4sddthhpecExTtzvvrVrxaOXbx4cS728ssvl56Ttd4JJ5yQi40dO7Zw7Jve9KZcbMGCBYVjL7zwwlxszpw5hWOLpvHSSy8Vji06OKAviq693tP1+y3Pa+hmZolwQzczS4QbuplZItzQzcwS4YZuZpYIRTTvmkKNuoBR0d27Z8yYUTh23LhxudiIESNKzwl6PhLg2muvzcWuuOKKXGzr1q2l55S6Wu6M3gi+OJc1Wi217TV0M7NEuKGbmSXCDd3MLBFu6GZmiUji1P/JkyfXFOurFStW5GKLFi0qHLt9+/ZcrKdT93u6vrOZWT28hm5mlgg3dDOzRLihm5klwg3dzCwRvTZ0SYdKWiJppaTHJJ2fxS+RtEbSQ9nXaY1P16w8rm1LTa+n/kvqAroi4kFJ+wM/A84EPgpsiYirap6ZT4+2BuvLqf+ubesktdR2LTeJ7ga6s8ebJa0EGnPxE7Mmcm1bavq0DV3SKOA4YFkWmi7pEUnXSxraw2vOlbRc0vK6MjVrINe2paDmqy1KGgLcB1weEbdIGg68AARwGZWPruf0Mg1/LLWG6s/VFl3b1glqqe2aGrqkvYFFwOKIuLrg+VHAoog4ppfpuOitofra0F3b1ilKuXyuJAHXASurCz7bobTTZODR/iRp1iqubUtNLUe5HA/8GPgFsCMLzwSmAGOofCx9GpiW7WTa3bS8FmMN1cejXFzb1jFK2+RSFhe9NZrvWGSp8h2LzMz2IG7oZmaJcEM3M0uEG7qZWSLc0M3MEuGGbmaWCDd0M7NEuKGbmSWi18vnluwF4FfZ47dkP6fGy9U6b2vhvHfWdif8nvor1WXrhOWqqbabeqboG2YsLY+IsS2ZeQN5ufZsKf+eUl22lJbLm1zMzBLhhm5mlohWNvR5LZx3I3m59mwp/55SXbZklqtl29DNzKxc3uRiZpaIpjd0SR+Q9ISkJyVd1Oz5lym7gfA6SY9WxYZJ+qGkX2bfC28w3M4kHSppiaSVkh6TdH4W7/hla6RUatt13XnLtlNTG7qkvYA5wKnAaGCKpNHNzKFkNwAf2CV2EXB3RBwB3J393Gm2AxdExFHAu4Hzsr9TCsvWEInV9g24rjtSs9fQxwFPRsSqiNgGfBeY1OQcShMRS4H1u4QnATdmj28EzmxqUiWIiO6IeDB7vBlYCYwggWVroGRq23Xdecu2U7Mb+gjg2aqfV2exlAzfef/J7PvBLc6nLtld748DlpHYspUs9dpO6m+fal03u6EX3RPPh9m0KUlDgJuBv4iITa3Op825tjtEynXd7Ia+Gji06ue3As81OYdGWyupCyD7vq7F+fSLpL2pFP23I+KWLJzEsjVI6rWdxN8+9bpudkN/ADhC0m9LGgR8DLi9yTk02u3Ap7LHnwJua2Eu/SJJwHXAyoi4uuqpjl+2Bkq9tjv+b78n1HXTTyySdBrwT8BewPURcXlTEyiRpAXABCpXa1sL/B1wK/A9YCTwDPCRiNh1B1Nbk3Q88GPgF8COLDyTyvbGjl62Rkqltl3XnbdsO/lMUTOzRPhMUTOzRLihm5klwg3dzCwRbuhmZolwQzczS4QbuplZItzQzcwS4YZuZpaI/wP4IrELobyAvAAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 2 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# get a sample image from training dataset\n",
    "img = train_data[0][0]\n",
    "\n",
    "# tranform sample image\n",
    "img_tr=data_transform(img)\n",
    "\n",
    "# convert tensor to numpy array\n",
    "img_tr_np=img_tr.numpy()\n",
    "\n",
    "# show original and transformed images\n",
    "plt.subplot(1,2,1)\n",
    "plt.imshow(img,cmap=\"gray\")\n",
    "plt.title(\"original\")\n",
    "plt.subplot(1,2,2)\n",
    "plt.imshow(img_tr_np[0],cmap=\"gray\");\n",
    "plt.title(\"transformed\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define transformations\n",
    "data_transform = transforms.Compose([\n",
    "                                        transforms.RandomHorizontalFlip(1),\n",
    "                                        transforms.RandomVerticalFlip(1),\n",
    "                                        transforms.ToTensor(),])\n",
    "\n",
    "# Loading MNIST training data with on-the-fly transformations\n",
    "train_data=datasets.MNIST(path2data, train=True, download=True,\n",
    "transform=data_transform )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Wrap Tensors into Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 28, 28]) 5\n"
     ]
    }
   ],
   "source": [
    "from torch.utils.data import TensorDataset\n",
    "\n",
    "# wrap tensors into a dataset\n",
    "train_ds = TensorDataset(x_train, y_train)\n",
    "val_ds = TensorDataset(x_val, y_val)\n",
    "\n",
    "for x,y in train_ds:\n",
    "    print(x.shape,y.item())\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Iterate Over Dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([8, 1, 28, 28])\n",
      "torch.Size([8])\n"
     ]
    }
   ],
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "# create a data loader from dataset\n",
    "train_dl = DataLoader(train_ds, batch_size=8)\n",
    "val_dl = DataLoader(val_ds, batch_size=8)\n",
    "\n",
    "# iterate over batches\n",
    "for xb,yb in train_dl:\n",
    "    print(xb.shape)\n",
    "    print(yb.shape)\n",
    "    break\n",
    "    # your training code will be here!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Building Models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([64, 100])\n"
     ]
    }
   ],
   "source": [
    "from torch import nn\n",
    "\n",
    "# input tensor dimension 64*1000\n",
    "input_tensor = torch.randn(64, 1000)\n",
    "\n",
    "# linear layer with 1000 inputs and 100 outputs\n",
    "linear_layer = nn.Linear(1000, 100)\n",
    "\n",
    "# output of the linear layer\n",
    "output = linear_layer(input_tensor)\n",
    "print(output.size())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define models using nn.Sequential"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sequential(\n",
      "  (0): Linear(in_features=4, out_features=5, bias=True)\n",
      "  (1): ReLU()\n",
      "  (2): Linear(in_features=5, out_features=1, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "from torch import nn\n",
    "\n",
    "# define a two-layer model\n",
    "model = nn.Sequential(\n",
    "                        nn.Linear(4, 5),\n",
    "                        nn.ReLU(), # relu is not shown in the figure.\n",
    "                        nn.Linear(5, 1),)\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Define models using nn.Module"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Net(\n",
      "  (conv1): Conv2d(1, 8, kernel_size=(5, 5), stride=(1, 1))\n",
      "  (conv2): Conv2d(8, 16, kernel_size=(5, 5), stride=(1, 1))\n",
      "  (fc1): Linear(in_features=256, out_features=100, bias=True)\n",
      "  (fc2): Linear(in_features=100, out_features=10, bias=True)\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "import torch.nn.functional as F\n",
    "class Net(nn.Module):\n",
    "    def __init__(self):\n",
    "        super(Net, self).__init__()\n",
    "        self.conv1 = nn.Conv2d(1, 8, 5, 1)\n",
    "        self.conv2 = nn.Conv2d(8, 16, 5, 1)\n",
    "        self.fc1 = nn.Linear(4*4*16, 100)\n",
    "        self.fc2 = nn.Linear(100, 10)\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = F.relu(self.conv1(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = F.relu(self.conv2(x))\n",
    "        x = F.max_pool2d(x, 2, 2)\n",
    "        x = x.view(-1, 4*4*16)\n",
    "        x = F.relu(self.fc1(x))\n",
    "        x = self.fc2(x)\n",
    "        return F.log_softmax(x, dim=1)\n",
    "\n",
    "model = Net()\n",
    "print(model)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Move Model to Device"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cpu\n"
     ]
    }
   ],
   "source": [
    "print(next(model.parameters()).device)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "device = torch.device(\"cuda:0\")\n",
    "model.to(device)\n",
    "print(next(model.parameters()).device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Show model summary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "----------------------------------------------------------------\n",
      "        Layer (type)               Output Shape         Param #\n",
      "================================================================\n",
      "            Conv2d-1            [-1, 8, 24, 24]             208\n",
      "            Conv2d-2             [-1, 16, 8, 8]           3,216\n",
      "            Linear-3                  [-1, 100]          25,700\n",
      "            Linear-4                   [-1, 10]           1,010\n",
      "================================================================\n",
      "Total params: 30,134\n",
      "Trainable params: 30,134\n",
      "Non-trainable params: 0\n",
      "----------------------------------------------------------------\n",
      "Input size (MB): 0.00\n",
      "Forward/backward pass size (MB): 0.04\n",
      "Params size (MB): 0.11\n",
      "Estimated Total Size (MB): 0.16\n",
      "----------------------------------------------------------------\n"
     ]
    }
   ],
   "source": [
    "from torchsummary import summary\n",
    "\n",
    "summary(model, input_size=(1, 28, 28))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Loss Function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "69.37257385253906\n"
     ]
    }
   ],
   "source": [
    "# define the negative log likelihood loss\n",
    "loss_func = nn.NLLLoss(reduction=\"sum\")\n",
    "\n",
    "for xb, yb in train_dl:\n",
    "    # move batch to cuda device\n",
    "    xb=xb.type(torch.float).to(device)\n",
    "    yb=yb.to(device)\n",
    "    # get model output\n",
    "    out=model(xb)\n",
    "    # calculate loss value\n",
    "    loss = loss_func(out, yb)\n",
    "    print (loss.item())\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [],
   "source": [
    "# compute gradients\n",
    "loss.backward()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Optimizer"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [],
   "source": [
    "from torch import optim\n",
    "\n",
    "# define Adam optimizer\n",
    "opt = optim.Adam(model.parameters(), lr=1e-4)\n",
    "\n",
    "# update model parameters\n",
    "opt.step()\n",
    "\n",
    "# set gradients to zero\n",
    "opt.zero_grad()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Training and Validation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "def metrics_batch(target, output):\n",
    "    # obtain output class\n",
    "    pred = output.argmax(dim=1, keepdim=True)\n",
    "    \n",
    "    # compare output class with target class\n",
    "    corrects=pred.eq(target.view_as(pred)).sum().item()\n",
    "    return corrects\n",
    "\n",
    "def loss_batch(loss_func, xb, yb,yb_h, opt=None):\n",
    "    \n",
    "    # obtain loss\n",
    "    loss = loss_func(yb_h, yb)\n",
    "    \n",
    "    # obtain performance metric\n",
    "    metric_b = metrics_batch(yb,yb_h)\n",
    "    \n",
    "    if opt is not None:\n",
    "        loss.backward()\n",
    "        opt.step()\n",
    "        opt.zero_grad()\n",
    "\n",
    "    return loss.item(), metric_b\n",
    "\n",
    "\n",
    "def loss_epoch(model,loss_func,dataset_dl,opt=None):\n",
    "    loss=0.0\n",
    "    metric=0.0\n",
    "    len_data=len(dataset_dl.dataset)\n",
    "    for xb, yb in dataset_dl:\n",
    "        xb=xb.type(torch.float).to(device)\n",
    "        yb=yb.to(device)\n",
    "        \n",
    "        # obtain model output\n",
    "        yb_h=model(xb)\n",
    "\n",
    "        loss_b,metric_b=loss_batch(loss_func, xb, yb,yb_h, opt)\n",
    "        loss+=loss_b\n",
    "        if metric_b is not None:\n",
    "            metric+=metric_b\n",
    "    loss/=len_data\n",
    "    metric/=len_data\n",
    "    return loss, metric\n",
    "\n",
    "\n",
    "\n",
    "def train_val(epochs, model, loss_func, opt, train_dl, val_dl):\n",
    "    for epoch in range(epochs):\n",
    "        model.train()\n",
    "        train_loss, train_metric=loss_epoch(model,loss_func,train_dl,opt)\n",
    "        \n",
    "            \n",
    "        model.eval()\n",
    "        with torch.no_grad():\n",
    "            val_loss, val_metric=loss_epoch(model,loss_func,val_dl)\n",
    "        \n",
    "        accuracy=100*val_metric\n",
    "\n",
    "        print(\"epoch: %d, train loss: %.6f, val loss: %.6f, accuracy: %.2f\" %(epoch, train_loss,val_loss,accuracy))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "epoch: 0, train loss: 0.294502, val loss: 0.093089, accuracy: 96.94\n",
      "epoch: 1, train loss: 0.080617, val loss: 0.061121, accuracy: 98.06\n",
      "epoch: 2, train loss: 0.050562, val loss: 0.049555, accuracy: 98.49\n",
      "epoch: 3, train loss: 0.035071, val loss: 0.049693, accuracy: 98.45\n",
      "epoch: 4, train loss: 0.025703, val loss: 0.050179, accuracy: 98.49\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "\n",
    "# call train_val function\n",
    "num_epochs=5\n",
    "train_val(num_epochs, model, loss_func, opt, train_dl, val_dl)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Store and Load Models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "\n",
    "# define path2weights\n",
    "path2weights=\"./models/weights.pt\"\n",
    "\n",
    "# store state_dict to file\n",
    "torch.save(model.state_dict(), path2weights)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Method 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Net(\n",
       "  (conv1): Conv2d(1, 8, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (conv2): Conv2d(8, 16, kernel_size=(5, 5), stride=(1, 1))\n",
       "  (fc1): Linear(in_features=256, out_features=100, bias=True)\n",
       "  (fc2): Linear(in_features=100, out_features=10, bias=True)\n",
       ")"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# define model: weights are randomly initiated\n",
    "_model = Net()\n",
    "\n",
    "# load weights from file\n",
    "weights=torch.load(path2weights)\n",
    "\n",
    "# set weights to model: weights are set with the store values\n",
    "_model.load_state_dict(weights)\n",
    "\n",
    "# set model in eval mode for deployment\n",
    "_model.eval()\n",
    "\n",
    "# model model to cuda device for accelerated computation\n",
    "_model.to(device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Method 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/home/ai1/anaconda2/envs/conda-pytorch/lib/python3.6/site-packages/torch/serialization.py:251: UserWarning: Couldn't retrieve source code for container of type Net. It won't be checked for correctness upon loading.\n",
      "  \"type \" + obj.__name__ + \". It won't be checked \"\n"
     ]
    }
   ],
   "source": [
    "# define a path2model\n",
    "path2model=\"./models/model.pt\"\n",
    "# store model and weights into local file\n",
    "torch.save(model,path2model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define model: weights are randomly initiated\n",
    "_model = Net()\n",
    "# load model and weights from local file\n",
    "_model=torch.load(path2model)\n",
    "\n",
    "# set model in eval mode for deployment\n",
    "_model.eval()\n",
    "# move model to cuda device for accelerated computation\n",
    "_model.to(device)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deploy Models"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 28, 28])\n",
      "6 6\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP8AAAD8CAYAAAC4nHJkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAADgpJREFUeJzt3X+MVfWZx/HPI4ImFBOViCiydBtduzGRbia6Caiz2UhwrYH+AVN/stnNTtFqFmNMif6ByabGbBbXTUwIQxw7TSilib9IsxEaop0aV8NATLXFtoSwMIIgodhpFMnA0z/mzO4U537PnXvPPedenvcrMffe89xz7+MNnznn3u8552vuLgDxXFB1AwCqQfiBoAg/EBThB4Ii/EBQhB8IivADQRF+ICjCDwR1YZlvZmYcTgi0mLtbPc9rastvZkvN7Ddmts/M1jbzWgDKZY0e229m0yT9VtLtkoYl7ZJ0t7v/OrEOW36gxcrY8t8kaZ+773f305J+LGlZE68HoETNhP9qSYcmPB7Olv0ZM+s1syEzG2rivQAUrJkf/CbbtfjSbr2790nqk9jtB9pJM1v+YUnXTHg8T9Lh5toBUJZmwr9L0rVm9lUzmyHp25K2FdMWgFZreLff3UfN7GFJ2yVNk9Tv7r8qrDMALdXwUF9Db8Z3fqDlSjnIB0DnIvxAUIQfCIrwA0ERfiAowg8EVer5/CjfunXrkvUHHnggWe/p6UnWh4Y4ZaNTseUHgiL8QFCEHwiK8ANBEX4gKMIPBMVQ33mgu7u7Zq23tze57meffZasd3V1JesM9XUutvxAUIQfCIrwA0ERfiAowg8ERfiBoAg/EBRX7+0As2bNStb3799fszYwMJBcd+3a9OTKef8+zpw5k6yjfFy9F0AS4QeCIvxAUIQfCIrwA0ERfiAowg8E1dT5/GZ2QNKIpDOSRt09ffI3GvLggw8m66dOnapZW79+fXLd0dHRhnpC5yviYh5/5+7HC3gdACVitx8Iqtnwu6QdZrbbzNLXiwLQVprd7V/k7ofN7ApJPzOzD919cOITsj8K/GEA2kxTW353P5zdHpP0iqSbJnlOn7t38WMg0F4aDr+ZzTSzWeP3JS2R9EFRjQForWZ2++dIesXMxl/nR+7+eiFdAWg5zufvAMePp0dSN27cWLP25JNPFt0O2hzn8wNIIvxAUIQfCIrwA0ERfiAowg8ExRTdbSDv0twXXXRRsv7hhx8W2Q6CYMsPBEX4gaAIPxAU4QeCIvxAUIQfCIrwA0Exzt8Gli5d2tT6r7/OZRQwdWz5gaAIPxAU4QeCIvxAUIQfCIrwA0ERfiAoxvnbwOrVq5P1L774Iln/5JNPimwHQbDlB4Ii/EBQhB8IivADQRF+ICjCDwRF+IGgcsf5zaxf0jclHXP3G7Jll0naKmmBpAOSVrr771vXZmczS8+YfPnllyfrO3fuLLKdttHd3Z2s9/T0NPX6J0+erFkbHBxMrpt3jYQyp7ZvlXq2/D+QdO7VJtZK2unu10ramT0G0EFyw+/ug5JOnLN4maSB7P6ApOUF9wWgxRr9zj/H3Y9IUnZ7RXEtAShDy4/tN7NeSb2tfh8AU9Polv+omc2VpOz2WK0nunufu3e5e1eD7wWgBRoN/zZJq7L7qyS9Vkw7AMqSG34z2yLpfyT9lZkNm9k/S3pG0u1m9jtJt2ePAXQQK3O80sw6f3C0AVdddVWyPjw8nKzfe++9yfqWLVum3FNRZsyYkaw/80zt7cKaNWuS6x48eDBZHxkZaXj9xYsXJ9ddsWJFsr5jx45kvUrunj6wJMMRfkBQhB8IivADQRF+ICjCDwRF+IGguHR3B6jy0twXXJDePmzatClZv//++2vWHnrooeS6L774YrKed0nzlOXL0+eibdy4MVlfuHBhsv7pp59OuaeyseUHgiL8QFCEHwiK8ANBEX4gKMIPBEX4gaAY5y/B/Pnzm1p/165dBXUydc8//3yyvmTJkobreZckb+Xp5tu3b0/WL7744mR95syZyTrj/ADaFuEHgiL8QFCEHwiK8ANBEX4gKMIPBMU4fwnmzJlTdQs1XXnllcn6XXfdlazfc889yfobb7wx5Z7K8Pnnnyfr+/btS9ZvueWWZH3r1q1T7qlsbPmBoAg/EBThB4Ii/EBQhB8IivADQRF+IKjccX4z65f0TUnH3P2GbNlTkv5F0vgF5Z9w9/9uVZOd7vTp002tP2/evGS9mXPH77vvvmQ97ziAt99+u+H37mSzZs2quoWm1bPl/4GkpZMs/093X5j9R/CBDpMbfncflHSihF4AlKiZ7/wPm9kvzazfzC4trCMApWg0/BskfU3SQklHJK2v9UQz6zWzITMbavC9ALRAQ+F396Pufsbdz0raJOmmxHP73L3L3bsabRJA8RoKv5nNnfDwW5I+KKYdAGWpZ6hvi6RuSbPNbFjSOkndZrZQkks6IOk7LewRQAvkht/d755k8Qst6OW89dZbbyXrH3/8cbK+evXqZP2RRx6Zck/j3nnnnWT9wgvT/0Ruu+22ZH3Hjh1T7qkMef9fl1xySbJ+8uTJItupBEf4AUERfiAowg8ERfiBoAg/EBThB4Li0t0lGBkZSdY/+uijZH3FihXJ+qOPPlqzNjo6mlz3xIn0OVtnz55N1qdNm5ast6u84dG8U5nzphfvBGz5gaAIPxAU4QeCIvxAUIQfCIrwA0ERfiAoc/fy3sysvDfrID09Pcn65s2bk/UNGzbUrDVzuq8k9fX1Jet33nlnst7f31+zdurUqYZ6Gpd3qvT8+fNr1jZt2pRc94477kjW23XqcUlyd6vneWz5gaAIPxAU4QeCIvxAUIQfCIrwA0ERfiAoxvk7wNatW5P15cuX16w999xzyXWfffbZZD1v+u+lSyebwPn/zZ49u2bNLD0cPWPGjGT9uuuuS9ZvvPHGmrXHHnssue7u3buT9XbGOD+AJMIPBEX4gaAIPxAU4QeCIvxAUIQfCCp3nN/MrpH0Q0lXSjorqc/d/8vMLpO0VdICSQckrXT33+e8FuP8DZg+fXqy/vTTT9esrVmzJrlu3pwBr776arJ+6NChZD0ldXyCJC1atChZz7t2/uOPP16z9t577yXX7WRFjvOPSnrM3b8u6W8lfdfM/lrSWkk73f1aSTuzxwA6RG743f2Iu+/J7o9I2ivpaknLJA1kTxuQlP4zDqCtTOk7v5ktkPQNSe9KmuPuR6SxPxCSrii6OQCtU/dcfWb2FUkvSVrj7n/IOy57wnq9knobaw9Aq9S15Tez6RoL/mZ3fzlbfNTM5mb1uZKOTbauu/e5e5e7dxXRMIBi5IbfxjbxL0ja6+4TTwHbJmlVdn+VpNeKbw9Aq9Qz1LdY0i8kva+xoT5JekJj3/t/Imm+pIOSVrh7cr5nhvrKd/PNNyfrK1euTNZvvfXWZP36669P1t98882atT179iTXHRwcTNbzLp+dN734+areob7c7/zu/pakWi/291NpCkD74Ag/ICjCDwRF+IGgCD8QFOEHgiL8QFBcuhs4z3DpbgBJhB8IivADQRF+ICjCDwRF+IGgCD8QFOEHgiL8QFCEHwiK8ANBEX4gKMIPBEX4gaAIPxAU4QeCIvxAUIQfCIrwA0ERfiAowg8ERfiBoAg/EFRu+M3sGjN7w8z2mtmvzOxfs+VPmdlHZvZe9t8/tL5dAEXJnbTDzOZKmuvue8xslqTdkpZLWinpj+7+H3W/GZN2AC1X76QdF9bxQkckHcnuj5jZXklXN9cegKpN6Tu/mS2Q9A1J72aLHjazX5pZv5ldWmOdXjMbMrOhpjoFUKi65+ozs69I+rmk77v7y2Y2R9JxSS7p3zT21eCfcl6D3X6gxerd7a8r/GY2XdJPJW1392cnqS+Q9FN3vyHndQg/0GKFTdRpZibpBUl7JwY/+yFw3LckfTDVJgFUp55f+xdL+oWk9yWdzRY/IeluSQs1ttt/QNJ3sh8HU6/Flh9osUJ3+4tC+IHWK2y3H8D5ifADQRF+ICjCDwRF+IGgCD8QFOEHgiL8QFCEHwiK8ANBEX4gKMIPBEX4gaAIPxBU7gU8C3Zc0v9OeDw7W9aO2rW3du1LordGFdnbX9T7xFLP5//Sm5sNuXtXZQ0ktGtv7dqXRG+Nqqo3dvuBoAg/EFTV4e+r+P1T2rW3du1LordGVdJbpd/5AVSn6i0/gIpUEn4zW2pmvzGzfWa2tooeajGzA2b2fjbzcKVTjGXToB0zsw8mLLvMzH5mZr/LbiedJq2i3tpi5ubEzNKVfnbtNuN16bv9ZjZN0m8l3S5pWNIuSXe7+69LbaQGMzsgqcvdKx8TNrNbJf1R0g/HZ0Mys3+XdMLdn8n+cF7q7t9rk96e0hRnbm5Rb7Vmlv5HVfjZFTnjdRGq2PLfJGmfu+9399OSfixpWQV9tD13H5R04pzFyyQNZPcHNPaPp3Q1emsL7n7E3fdk90ckjc8sXelnl+irElWE/2pJhyY8HlZ7TfntknaY2W4z6626mUnMGZ8ZKbu9ouJ+zpU7c3OZzplZum0+u0ZmvC5aFeGfbDaRdhpyWOTufyPpDknfzXZvUZ8Nkr6msWncjkhaX2Uz2czSL0la4+5/qLKXiSbpq5LPrYrwD0u6ZsLjeZIOV9DHpNz9cHZ7TNIrGvua0k6Ojk+Smt0eq7if/+PuR939jLuflbRJFX522czSL0na7O4vZ4sr/+wm66uqz62K8O+SdK2ZfdXMZkj6tqRtFfTxJWY2M/shRmY2U9IStd/sw9skrcrur5L0WoW9/Jl2mbm51szSqviza7cZrys5yCcbynhO0jRJ/e7+/dKbmISZ/aXGtvbS2BmPP6qyNzPbIqlbY2d9HZW0TtKrkn4iab6kg5JWuHvpP7zV6K1bU5y5uUW91ZpZ+l1V+NkVOeN1If1whB8QE0f4AUERfiAowg8ERfiBoAg/EBThB4Ii/EBQhB8I6k8P+z+a3okJGgAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "# x is a data point with C*H*W shape\n",
    "n=100\n",
    "x= x_val[n]\n",
    "y=y_val[n]\n",
    "print(x.shape)\n",
    "plt.imshow(x.numpy()[0],cmap=\"gray\")\n",
    "\n",
    "# we use unsqueeze to expand dimensions to 1*C*H*W\n",
    "x= x.unsqueeze(0)\n",
    "\n",
    "# convert to torch.float32\n",
    "x=x.type(torch.float)\n",
    "\n",
    "# move to cuda device\n",
    "x=x.to(device)\n",
    "\n",
    "# get model output\n",
    "output=_model(x)\n",
    "\n",
    "# get predicted class\n",
    "pred = output.argmax(dim=1, keepdim=True)\n",
    "print (pred.item(),y.item())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
